This tutorial will guide you through a simple joynr radio application, explaining three essential joynr concepts:
- A simple radio communication interface
- A consumer interested in knowing about radio information
- A provider, that provides the radio information
If you haven't built joynr yet, please do so first:
- Building joynr Java for the Java example only.
- Building joynr C++ for the C++ example.
This will install the necessary dependencies to your local Maven repository and generate the radio application source files.
The example project contains a java and c++ variation, letting you explore whichever one you find more comfortable.
The RadioApp demo is located in <JOYNR>/examples/radio-app
. We refer to this location as
RADIO_HOME
.
For exploring the Java code and for viewing the radio communication interface, you can use Eclipse
and import the RadioApp (<RADIO_HOME>/pom.xml
) as a Maven project, using the M2E plugin. For C++,
open <RADIO_HOME>/CMakeLists.txt
in QtCreator.
Note: Dependency Resolution
In Java all dependencies are resolved automatically using Maven. In C++ joynr must be built from the sources. Afterwards, joynr will be resolved using CMake's
find_package
command:# Pull in the joynr configuration find_package(joynr REQUIRED) message(STATUS "joynr variable JOYNR_INCLUDE_DIRS=${JOYNR_INCLUDE_DIRS}") message(STATUS "joynr variable JOYNR_LIBRARIES=${JOYNR_LIBRARIES}")
With your environment set up, and the RadioApp demo project in your workspace, we are now ready to explain the three essential ingredients in this demonstration application.
Communication interfaces are contracts between providers and consumers defining the data and methods that should be exchanged/communicated via the joynr framework. The communication interfaces are defined using Franca IDL - from which, supporting joynr library code is generated to enable joynr communication.
Within this project, we have created a radio communication interface. This is located within:
<RADIO_HOME>/src/main/model
. Within this folder you will see the file: radio.fidl
, open it.
If a prompt appears asking you whether you want to add the XtextNature, select to add it. This
interface describes the methods and what types of data can be exchanged or subscribed to.
If you have made changes to the communication interface, the next step is to generate the necessary joynr library code so that you can start programming your radio interface. This generation process creates all the necessary code that joynr uses to establish communication.
To generate the source code for the interface, right click on your project and select Maven->Update Project....
Note: It is also possible to trigger the code generation process from the command line by running
mvn generate-sources
. Code generation is triggered by the joynr-generator-maven-plugin. The plugin is configured in the plugin section of the <RADIO_HOME>/pom.xml file:
- the custom model is loaded from the
<RADIO_HOME>/src/main/model
directory specified in the model plugin configuration- code generation templates for Java and C++ are loaded from the classpath through the
io.joynr.java:java-generator
andio.joynr.cpp:cpp-generator
dependencies- there are two separate plugin executions: one to generate Java and one to generate C++
Now refresh the project folder by hitting F5 and navigate to the
<RADIO_HOME>/src/main/generated-java
and <RADIO_HOME>/src/main/generated-cpp
folders.
All these files within the <RADIO_HOME>/src/main/generated-java
and
<RADIO_HOME>/src/main/generated-cpp
folder are the joynr interfaces and classes that you will
require for programming your radio functionality. If you want to communicate between your smart
phone and vehicle, then these files need to be present on both the smart phone, and on the vehicle
side.
A provider is a class that is responsible for being the data source, and supplies data to any
consumers who are interested. Have a look into the provider classes and see how they implement the
addFavoriteStation
method defined in the <RADIO_HOME>/src/main/model/radio.fidl
file. They
inherit from the generated classes joynr.vehicle.RadioAbsctractProvider
(located in
<RADIO_HOME>/src/main/generated-java/joynr/vehicle/RadioAbstractProvider.java
) and
joyn::vehicle::RadioProvider
(located in
<RADIO_HOME>/src/main/generated-cpp/include/joynr/vehicle/RadioProvider.h
and
<RADIO_HOME>/src/main/generated-cpp/provider/generated/vehicle/RadioProvider.cpp
).
Java: [<RADIO_HOME>/src/main/java/io/joynr/demo/MyRadioProvider.java] (/examples/radio-app/src/main/java/io/joynr/demo/MyRadioProvider.java)
...
public class MyRadioProvider extends RadioAbstractProvider {
...
@Override
public Promise<AddFavoriteStationDeferred> addFavoriteStation(RadioStation radioStation) {
AddFavoriteStationDeferred deferred = new AddFavoriteStationDeferred();
LOG.info(PRINT_BORDER + "addFavoriteStation(" + radioStation + ")" + PRINT_BORDER);
stationsList.add(radioStation);
deferred.resolve(true);
return new Promise<AddFavoriteStationDeferred>(deferred);
}
...
}
C++: [<RADIO_HOME>/src/main/cpp/MyRadioProvider.h] (/examples/radio-app/src/main/cpp/MyRadioProvider.h)
...
class MyRadioProvider : public joyn::vehicle::DefaultRadioProvider {
public:
...
void addFavoriteStation(
const joynr::vehicle::RadioStation& newFavoriteStation,
std::function<void(const bool&)> onSuccess,
std::function<void(const joynr::vehicle::Radio::AddFavoriteStationErrorEnum::Enum&)> onError);
...
}
C++: [<RADIO_HOME>/src/main/cpp/MyRadioProvider.cpp] (/examples/radio-app/src/main/cpp/MyRadioProvider.cpp)
...
void MyRadioProvider::addFavoriteStation(
const vehicle::RadioStation& radioStation,
std::function<void(const bool& returnValue)> onSuccess,
std::function<void(const joynr::vehicle::Radio::AddFavoriteStationErrorEnum::Enum&)> onError
)
{
std::lock_guard<std::mutex> locker(mutex);
MyRadioHelper::prettyLog(
logger, "addFavoriteStation(" + radioStation.toString() + ")");
stationsList.push_back(radioStation);
onSuccess(true);
}
...
Have a look at the implementation of these providers, you could imagine writing code here which links directly to a vehicle/device or to the actual radio station a user is listening to on their phone.
Provider objects are created and registered at joynr in MyRadioProviderApplication
. The main
method expects the domain to register the provider with as command line argument. The application
can be stopped by hitting q
. The provider will be unregistered and the application stops.
C++: [<RADIO_HOME>/src/main/cpp/MyRadioProviderApplication.cpp] (/examples/radio-app/src/main/cpp/MyRadioProviderApplication.cpp)
...
// onFatalRuntimeError callback is optional, but it is highly recommended to provide an
// implementation.
bool isRuntimeOkay = true;
std::function<void(const joynr::exceptions::JoynrRuntimeException&)> onFatalRuntimeError =
[&] (const joynr::exceptions::JoynrRuntimeException& exception) {
isRuntimeOkay = false;
MyRadioHelper::prettyLog(logger, "Unexpected joynr runtime error occurred: " + exception.getMessage());
};
std::shared_ptr<JoynrRuntime> runtime = JoynrRuntime::createRuntime(pathToLibJoynSettings,
onFatalRuntimeError, pathToMessagingSettings);
// Initialize the quality of service settings
// Set the priority so that the consumer application always uses the most recently started provider
std::chrono::milliseconds millisSinceEpoch =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
types::ProviderQos providerQos;
providerQos.setPriority(millisSinceEpoch.count());
// Register the provider
std::shared_ptr<MyRadioProvider> provider(new MyRadioProvider());
runtime->registerProvider<vehicle::RadioProvider>(providerDomain, provider, providerQos);
...
if (isRuntimeOkay) {
// Unregister the provider
runtime->unregisterProvider<vehicle::RadioProvider>(providerDomain, provider);
}
return isRuntimeOkay ? 0 : 1;
Java: [<RADIO_HOME>/src/main/java/io/joynr/demo/MyRadioProviderApplication.java] (/examples/radio-app/src/main/java/io/joynr/demo/MyRadioProviderApplication.java)
...
public static void main(String[] args) {
...
JoynrApplication joynrApplication = new JoynrInjectorFactory(joynrConfig, runtimeModule)
.createApplication(new JoynrApplicationModule(MyRadioProviderApplication.class, appConfig) {
...
});
joynrApplication.run();
joynrApplication.shutdown();
}
@Override
public void run() {
provider = new MyRadioProvider(providerScope);
ProviderQos providerQos = new ProviderQos();
providerQos.setPriority(System.currentTimeMillis());
runtime.registerProvider(localDomain, provider, providerQos);
}
@Override
public void shutdown() {
if (provider != null) {
runtime.unregisterProvider(localDomain, provider);
}
runtime.prepareForShutdown();
runtime.shutdown(true);
}
...
The following keyboard commands can be used to control the provider application:
q
to quits
to shuffle stationsw
to fire weak signal eventp
to fire weak signal event with country of current station as partitionn
to fire new station discovered eventm
to print status metrics (Note: ConnectionStatusMetrics are only available for HivemqMqttClient)
A consumer is a class that wants to retrieve data from providers, they can either subscribe to data
attribute changes or query providers by calling their operations. In the Radio Demo App you will
find MyRadioConsumerApplication
class. Open it.
In this demo, we will demonstrate how a consumer can make a call to a provider. Imagine the consumer lives on your smart phone, and the provider living on your vehicle. Have a look at the run method. Step through the code to get a feel for the necessary steps in instantiating a proxy that is used to communicate to your provider. Following from the instantiation of the radio proxy, various calls are made on the proxy to retrieve different information. Feel free to add a call to the new operation you added to the interface, and add a print statement so that you can see the result.
C++: [<RADIO_HOME>/src/main/cpp/MyRadioConsumerApplication.cpp] (/examples/radio-app/src/main/cpp/MyRadioConsumerApplication.cpp)
...
int main(int argc, char* argv[])
{
...
// onFatalRuntimeError callback is optional, but it is highly recommended to provide an
// implementation.
bool isRuntimeOkay = true;
std::function<void(const joynr::exceptions::JoynrRuntimeException&)> onFatalRuntimeError =
[&] (const joynr::exceptions::JoynrRuntimeException& exception) {
isRuntimeOkay = false;
MyRadioHelper::prettyLog(logger, "Unexpected joynr runtime error occurred: " + exception.getMessage());
};
std::shared_ptr<JoynrRuntime> runtime =
JoynrRuntime::createRuntime(pathToMessagingSettings, onFatalRuntimeError);
// Create proxy builder
std::unique_ptr<ProxyBuilder<vehicle::RadioProxy>> proxyBuilder =
runtime->createProxyBuilder<vehicle::RadioProxy>(providerDomain);
// Messaging Quality of service
std::int64_t qosMsgTtl = 30000; // Time to live is 30 secs in one direction
std::int64_t qosCacheDataFreshnessMs = 400000; // Only consider data cached for < 400 secs
// Find the provider with the highest priority set in ProviderQos
DiscoveryQos discoveryQos;
// As soon as the discovery QoS is set on the proxy builder, discovery of suitable providers
// is triggered. If the discovery process does not find matching providers within the
// discovery timeout duration, discovery will be terminated and you will get a discovery exception.
discoveryQos.setDiscoveryTimeoutMs(40000);
// Provider entries in the global capabilities directory are cached locally. Discovery will
// consider entries in this cache valid if they are younger as the max age of cached
// providers as defined in the QoS. All valid entries will be processed by the arbitrator when
// searching
// for and arbitrating the "best" matching provider.
// NOTE: Valid cache entries might prevent triggering a lookup in the global capabilities
// directory. Therefore, not all providers registered with the global capabilities
// directory might be taken into account during arbitration.
discoveryQos.setCacheMaxAgeMs(std::numeric_limits<qint64>::max());
// The discovery process outputs a list of matching providers. The arbitration strategy then
// chooses one or more of them to be used by the proxy.
discoveryQos.setArbitrationStrategy(DiscoveryQos::ArbitrationStrategy::HIGHEST_PRIORITY);
// Build a proxy
std::unique_ptr<vehicle::RadioProxy> proxy = proxyBuilder->setMessagingQos(MessagingQos(qosMsgTtl))
->setCached(false)
->setDiscoveryQos(discoveryQos)
->build();
vehicle::RadioStation currentStation;
try {
proxy->getCurrentStation(status, currentStation);
} catch (exceptions::JoynrException& e) {
assert(false);
}
MyRadioHelper::prettyLog(logger, "ATTRIBUTE GET: " + currentStation.toString())
// add favorite radio station
vehicle::RadioStation favoriteStation("99.3 The Fox Rocks", false, vehicle::Country::CANADA);
bool success;
try {
proxy->addFavoriteStation(success, favoriteStation);
MyRadioHelper::prettyLog(
logger,
"METHOD: added favorite station: " + favoriteStation.toString());
} catch (exceptions::ApplicationException e) {
...
}
// shuffle the stations
MyRadioHelper::prettyLog(logger, "METHOD: calling shuffle stations");
proxy->shuffleStations();
}
return isRuntimeOkay ? 0 : 1;
...
Java: [<RADIO_HOME>/src/main/java/io/joynr/demo/MyRadioConsumerApplication.java] (/examples/radio-app/src/main/java/io/joynr/demo/MyRadioConsumerApplication.java)
...
public static void main(String[] args) throws IOException {
...
JoynrApplication myRadioConsumerApp = new JoynrInjectorFactory(joynrConfig, runtimeModule).createApplication(new JoynrApplicationModule(MyRadioConsumerApplication.class,
appConfig) {
...
});
myRadioConsumerApp.run();
myRadioConsumerApp.shutdown();
}
public void run() {
DiscoveryQos discoveryQos = new DiscoveryQos();
// As soon as the discovery QoS is set on the proxy builder, discovery of suitable providers
// is triggered. If the discovery process does not find matching providers within the
// discovery timeout duration it will be terminated and you will get a discovery exception.
discoveryQos.setDiscoveryTimeoutMs(10000);
// Provider entries in the global capabilities directory are cached locally. Discovery will
// consider entries in this cache valid if they are younger as the max age of cached
// providers as defined in the QoS. All valid entries will be processed by the arbitrator when searching
// for and arbitrating the "best" matching provider.
// NOTE: Valid cache entries might prevent triggering a lookup in the global capabilities
// directory. Therefore, not all providers registered with the global capabilities
// directory might be taken into account during discovery.
discoveryQos.setCacheMaxAgeMs(Long.MAX_VALUE);
// The discovery process outputs a list of matching providers. The arbitration strategy then
// chooses one or more of them to be used by the proxy.
discoveryQos.setArbitrationStrategy(ArbitrationStrategy.HighestPriority);
// The provider will maintain at least a minimum interval idle time in milliseconds between
// successive notifications, even if on-change notifications are enabled and the value changes more
// often. This prevents the consumer from being flooded by updated values. The filtering happens on
// the provider's side, thus also preventing excessive network traffic.
int minIntervalMs = 0;
// The provider will send notifications every maximum interval in milliseconds, even if the value didn't
// change. It will send notifications more often if on-change notifications are enabled,
// the value changes more often, and the minimum interval QoS does not prevent it. The maximum interval
// can thus be seen as a sort of heart beat.
int maxIntervalMs = 10000;
// The provider will send notifications until the end date is reached. The consumer will not receive any
// notifications (neither value notifications nor missed publication notifications) after
// this date.
long expiryDateMs = System.currentTimeMillis() + 60000;
// If no notification was received within the last alert interval, a missed publication
// notification will be raised.
int alertAfterIntervalMs = 20000;
// Notification messages will be sent with this time-to-live. If a notification message can not be
// delivered within its TTL, it will be deleted from the system.
// NOTE: If a notification message is not delivered due to an expired TTL, it might raise a
// missed publication notification (depending on the value of the alert interval QoS).
int publicationTtlMs = 5000;
OnChangeWithKeepAliveSubscriptionQos subscriptionQos = new OnChangeWithKeepAliveSubscriptionQos();
subscriptionQos.setMinIntervalMs(minInterval_ms).setMaxIntervalMs(maxInterval_ms).setExpiryDateMs(validityMs);
subscriptionQos.setAlertAfterIntervalMs(alertAfterInterval_ms).setPublicationTtlMs(publicationTtl_ms);
ProxyBuilder<RadioProxy> proxyBuilder = runtime.getProxyBuilder(providerDomain, RadioProxy.class);
try {
// getting an attribute
radioProxy = proxyBuilder.setMessagingQos(new MessagingQos()).setDiscoveryQos(discoveryQos).build();
RadioStation currentStation = radioProxy.getCurrentStation();
LOG.info(PRINT_BORDER + "ATTRIBUTE GET: current station: " + currentStation + PRINT_BORDER);
// subscribe to an attribute
subscriptionFutureCurrentStation = radioProxy.subscribeToCurrentStation(new AttributeSubscriptionListener<RadioStation>() {
@Override
public void onReceive(RadioStation value) {
LOG.info(PRINT_BORDER
+ "ATTRIBUTE SUBSCRIPTION: current station: "
+ value + PRINT_BORDER);
}
@Override
public void onError(JoynrRuntimeException error) {
LOG.info(PRINT_BORDER
+ "ATTRIBUTE SUBSCRIPTION: " + error
+ PRINT_BORDER);
}
},
subscriptionQos);
...
// add favorite radio station
RadioStation favoriteStation = new RadioStation("99.3 The Fox Rocks", false, Country.CANADA);
success = radioProxy.addFavoriteStation(favoriteStation);
LOG.info(PRINT_BORDER + "METHOD: added favorite station: " + favoriteStation + ": " + success
+ PRINT_BORDER);
// shuffle the stations
radioProxy.shuffleStations();
currentStation = radioProxy.getCurrentStation();
LOG.info(PRINT_BORDER + "The current radio station after shuffling is: " + currentStation + PRINT_BORDER);
...
} catch (DiscoveryException e) {
} catch (JoynCommunicationException e) {
}
}
...
The following keyboard commands can be used to control the consumer application:
q
to quits
to shuffle stationsg
to get the current stationl
to get the location of the current stationm
to print status metrics (Note: ConnectionStatusMetrics are only available for HivemqMqttClient)
The radio app can be run in all combinations of consumer and provider: java-java, cpp-cpp, java-cpp, and cpp-java.
You need to have Maven installed. Joynr is tested with Maven 3.3.3, but more recent versions should also work here.
For both, consumer and provider, the backend (Global Capabilities Directory) has to be started first. You will also need to install an MQTT broker, e.g. Mosquitto.
Please refer to the starting joynr backend instructions
After importing <RADIO_HOME>/pom.xml
into Eclipse using the M2E plugin, Eclipse will automatically
resolve dependencies through Maven and build the project.
Now to run the example, first start the provider, open the MyRadioProviderApplication class, right click and Run as Java Application. The application will fail to run because the provider domain must be set on the command line. Right click again and select Run Configurations... Go to the Arguments tab and enter the provider domain. Then press Apply and then Run.
Alternatively, run the provider from the command line by executing the following Maven command:
<RADIO_HOME>$ mvn exec:java -Dexec.mainClass="io.joynr.demo.MyRadioProviderApplication" -Dexec.args="-d <my provider domain>"
Note: The provider domain is used to register the MyRadioProvider on this domain. Consumers must specify this domain when creating a proxy for the radio interface in order to use the previously registered provider.
Now run the MyRadioConsumerApplication class and right click and select Run as Java Application. Add the same provider domain to the run configuration. This consumer will make a call to the joynr runtime to find a provider with the domain. If there are several providers of the same type registered on the same domain, then the ArbitrationStrategy (see in the run method of MyRadioConsumerApplication class) is used to work out which provider to take. In the console, you should be able to see log output.
Alternatively, run the consumer from the command line by executing the following Maven command:
<RADIO_HOME>$ mvn exec:java -Dexec.mainClass="io.joynr.demo.MyRadioConsumerApplication" -Dexec.args="-d <my provider domain>"
Pick a domain that will be used to identify the provider and run the example:
Running the Provider
<CPP_BUILD_DIRECTORY>/radio/bin$ ./radio-app-provider-cc <my provider domain>
In another terminal window execute:
Running the Consumer
<CPP_BUILD_DIRECTORY>/radio/bin$ ./radio-app-consumer-cc <my provider domain>
To use a standalone cluster-controller start
<CPP_BUILD_DIRECTORY>/radio/bin$ ./cluster-controllerThen you can start provider-ws and consumer-ws which establish a websocket connection to that standalone cluster controller (instead of the cc variants which use an embedded cluster controller):
<CPP_BUILD_DIRECTORY>/radio/bin$ ./radio-app-provider-ws <my provider domain> <CPP_BUILD_DIRECTORY>/radio/bin$ ./radio-app-consumer-ws <my provider domain>For UNIX domain socket connection to standalone cluster controller you can start provider-uds and consumer-uds:
<CPP_BUILD_DIRECTORY>/radio/bin$ ./radio-app-provider-uds <my provider domain> <CPP_BUILD_DIRECTORY>/radio/bin$ ./radio-app-consumer-uds <my provider domain>
The consumer will make a call to the joynr runtime to find a provider with the domain. If there are several providers of the same type registered on the same domain, then the ArbitrationStrategy (see in the main function of MyRadioConsumerApplication.cpp) is used to work out which provider to take.
Note: Since the C++ radio application has been built using docker ( Building joynr C++), the shared joynr libraries are only found if your build directory is
/data/build/
as in the docker container. After building joynr C++ with docker, you can either copy your build directory to this location or add the following directories to your library path:
<CPP_BUILD_DIRECTORY>/joynr/bin
In Linux, this can be achieved by
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<BUILD_DIRECTORY>/joynr/bin
Afterwards, the C++ radio application can be started as explained.
In this tutorial, you have seen a communication interface, generated joynr code from it, adapted a provider and consumer, and seen the communication between the two in action. The next step is to create your own interface entirely and create more providers and consumers yourself. Use this project as a template for your further investigations!
- Using selective broadcast to implement a geocast: In that tutorial the example from here is extended by a selective broadcast and filter logics that implements a geocast.