About API Guide - C++
These APIs were added to AllJoyn™ 14.12 release for using the About Feature in an older release please see: Legay C++ About API Guide
Reference code
Classes used to send AboutData
Class | Description |
---|---|
AboutObj |
Class that implements the org.alljoyn.About interface as a BusObject . |
AboutIconObj |
Class that implements the org.alljoyn.Icon interface as a BusObject . |
AboutDataListener |
Interface that supplies the MsgArg containing the AboutData fields required for the Announce signal payload and GetAboutData() . |
AboutData |
A default implementation of the AboutDataListener interface. For most developers this implementation will be sufficient. |
AboutIcon |
A container class that holds the icon sent by the AboutIconObj |
Classes used to receive AboutData
Class | Description |
---|---|
AboutProxy |
Class used to get proxy access to the AboutObj. |
AboutIconProxy |
Class used to get proxy access to the AboutIconObj. |
AboutListener |
Abstract base class implemented by AllJoyn users to receive About interface related events |
AboutData |
A default implementation of the AboutDataListener interface. This class can be used to read the contents of an org.alljoyn.About.Announce signal |
AboutObjectDescription |
A helper class for accessing the fields of the ObjectDescription MsgArg that is sent as part of the org.alljoyn.About.Announce signal |
BusAttachment |
Used to register AboutListener s and specify interfaces of interest |
Setting Up an application to send an Announce
signal
The following is the high-level process to build an application that will
broadcast an Announce
signal. Steps marked with a * are unique to
applications using the About Feature.
- Create a
BusAttachment
- Start
- Connect
- Bind session port
- Other setup for security etc
- Create interfaces
- Create
BusObject
s for interfaces - When Adding interfaces to the
BusObject
mark it asANNOUNCED
* - Register the
BusObject
s with theBusAttachment
- Fill in your
AboutData
* - Create an
AboutObj
* - Call
AboutObj::Announce(sessionPort, aboutData)
*
Setting Up the AllJoyn Framework to receive an Announce
signal
The following is the high-level process to build an application that will
receive an Announce
signal. Steps marked with a * are unique to applications
using the About Feature.
- Create a
BusAttachment
- Start
- Connect
- Other setup for security etc
- Create an
AboutListener
* - Register the new
AboutListener
* - call
BusAttachment::WhoImplements
member function to specify interfaces your application is interested in.*
Sample code for sending an Announce
signal
Create a BusAttachment
Create a new BusAttachment.
BusAttachment bus("About Service Example");
Start the BusAttachment and Connect to the routing node.
status = bus.Start();
if (ER_OK != status) {
printf("FAILED to start BusAttachment (%s)\n", QCC_StatusText(status));
exit(1);
}
status = bus.Connect();
if (ER_OK != status) {
printf("FAILED to connect to router node (%s)\n", QCC_StatusText(status));
exit(1);
}
Bind a session port that will be used to communicate. the value for
ASSIGNED_SESSION_PORT
is chosen by the developer. The value itself is
unimportant. What is important is that the session port bound to is part of the
Announce
signal.
SessionOpts opts(SessionOpts::TRAFFIC_MESSAGES, false, SessionOpts::PROXIMITY_ANY, TRANSPORT_ANY);
SessionPort sessionPort = ASSIGNED_SESSION_PORT;
MySessionPortListener sessionPortListener;
bus.BindSessionPort(sessionPort, opts, sessionPortListener);
if (ER_OK != status) {
printf("Failed to BindSessionPort (%s)", QCC_StatusText(status));
}
Create interfaces
The interface is a collection methods, signals, and properties. The interface can be specified in code or using xml notation.
For this sample the following xml interface was used.
<interface name='com.example.about.feature.interface.sample' >
<method name='Echo'>
<arg name='out_arg' type='s' direction='in' />
<arg name='return_arg' type='s' direction='out' />
</method>
</interface>
C++ code showing adding the interface to the BusAttachment using xml. The
INTERFACE_NAME
is coded to be the string
com.example.about.feature.interface.sample
.
qcc::String interface = "<node>"
"<interface name='" + qcc::String(INTERFACE_NAME) + "'>"
" <method name='Echo'>"
" <arg name='out_arg' type='s' direction='in' />"
" <arg name='return_arg' type='s' direction='out' />"
" </method>"
"</interface>"
"</node>";
status = bus.CreateInterfacesFromXml(interface.c_str());
if (ER_OK != status) {
printf("Failed to parse the xml interface definition (%s)", QCC_StatusText(status));
exit(1);
}
Alternative C++ code showing adding the interface with out using xml notation.
/* Add org.alljoyn.Bus.method_sample interface */
InterfaceDescription* intf = NULL;
status = bus.CreateInterface(INTERFACE_NAME, intf);
if (status == ER_OK) {
printf("Interface created.\n");
intf->AddMethod("Echo", "s", "s", "out_arg,return_arg", 0);
intf->Activate();
} else {
printf("Failed to create interface '%s'.\n", INTERFACE_NAME);
}
Create BusObject
s for interfaces
Sample implementation of a BusObject that announces the interface defined above.
When adding the interface to the BusObject you can specify if that interface is
announced by adding the ANNOUNCED
value to the AddInterface()
member
function.
NOTE: The BusObject adds method handlers for methods specified in the
com.example.about.feature.interface.sample
interface. If it contained any
properties it would also be responsible for add Get/Set handler functions for
the properties as well. The code lets the object path be passed in at
runtime. The path could have also been hard coded into the BusObject.
class MyBusObject : public BusObject {
public:
MyBusObject(BusAttachment& bus, const char* path)
: BusObject(path) {
QStatus status;
const InterfaceDescription* iface = bus.GetInterface(INTERFACE_NAME);
assert(iface != NULL);
// Here the value ANNOUNCED tells AllJoyn that this interface
// should be announced
status = AddInterface(*iface, ANNOUNCED);
if (status != ER_OK) {
printf("Failed to add %s interface to the BusObject\n", INTERFACE_NAME);
}
/* Register the method handlers with the object */
const MethodEntry methodEntries[] = {
{ iface->GetMember("Echo"), static_cast<MessageReceiver::MethodHandler>(&MyBusObject::Echo) }
};
AddMethodHandlers(methodEntries, sizeof(methodEntries) / sizeof(methodEntries[0]));
}
// Respond to remote method call `Echo` by returning the string back to the
// sender.
void Echo(const InterfaceDescription::Member* member, Message& msg) {
printf("Echo method called: %s", msg->GetArg(0)->v_string.str);
const MsgArg* arg((msg->GetArg(0)));
QStatus status = MethodReply(msg, arg, 1);
if (status != ER_OK) {
printf("Failed to created MethodReply.\n");
}
}
};
Register the BusObjects
with the BusAttachment
MyBusObject busObject(bus, "/example/path");
status = bus.RegisterBusObject(busObject);
if (ER_OK != status) {
printf("Failed to register BusObject (%s)", QCC_StatusText(status));
exit(1);
}
AboutData fields
Field name | Required | Announced | Localized | Signature |
---|---|---|---|---|
AppId |
yes | yes | no | ay |
DefaultLanguage |
yes | yes | no | s |
DeviceName |
no | yes | yes | s |
DeviceId |
yes | yes | no | s |
AppName |
yes | yes | yes | s |
Manufacturer |
yes | yes | yes | s |
ModelNumber |
yes | yes | no | s |
SupportedLanguages |
yes | no | no | as |
Description |
yes | no | yes | s |
DateofManufacture |
no | no | no | s |
SoftwareVersion |
yes | no | no | s |
AJSoftwareVersion |
yes | no | no | s |
HardwareVersion |
no | no | no | s |
SupportUrl |
no | no | no | s |
Fields marked as Announced are part of the Announce
signal. If a value is not
announced then you must use the org.alljoyn.About.GetAboutData
method to
access those values.
Fields marked as Required must all be supplied to send an Announce
signal.
They are required even if the value is not part of the Announce
signal.
Fields marked as Localized should supply localization values for every language
listed in the SupportedLanguages
AppId
is a 128-bit UUID (16-bytes) as specified in RFC 4122.
Fill in your AboutData
The AboutData
is an instance of the AboutDataListener
interface. For most
developers, the AboutData
will provide the AboutDataListener
dictionary of
key/value pairs (a{sv}
). This is needed to send an `Announce signal.
// Setup the about data
// The default language is specified in the constructor. If the default language
// is not specified any Field that should be localized will return an error
AboutData aboutData("en");
//AppId is a 128bit uuid
uint8_t appId[] = { 0x01, 0xB3, 0xBA, 0x14,
0x1E, 0x82, 0x11, 0xE4,
0x86, 0x51, 0xD1, 0x56,
0x1D, 0x5D, 0x46, 0xB0 };
aboutData.SetAppId(appId, 16);
aboutData.SetDeviceName("My Device Name");
//DeviceId is a string encoded 128bit UUIDf
aboutData.SetDeviceId("93c06771-c725-48c2-b1ff-6a2a59d445b8");
aboutData.SetAppName("Application");
aboutData.SetManufacturer("Manufacturer");
aboutData.SetModelNumber("123456");
aboutData.SetDescription("A poetic description of this application");
aboutData.SetDateOfManufacture("2014-03-24");
aboutData.SetSoftwareVersion("0.1.2");
aboutData.SetHardwareVersion("0.0.1");
aboutData.SetSupportUrl("http://www.example.org");
Localized values like DeviceName
, AppName
, etc are automatically set to the
default language specified in the constructor unless a different language tag
is passed in when setting the values. For example, to add the Spanish language
to the AboutData
the following would be done. All strings must be UTF-8
encoded.
aboutData.SetDeviceName("Mi dispositivo Nombre", "es");
aboutData.SetAppName("aplicación", "es");
aboutData.SetManufacturer("fabricante", "es");
aboutData.SetDescription("Una descripción poética de esta aplicación", "es");
Any new language specified, including the default language, is automatically
added to the SupportedLanguages
by the AboutData
implementation.
The AJSoftwareVersion
is also automatically filled in by the AboutData
implementation.
Create an AboutObj
and Announce
AboutObj aboutObj(bus);
status = aboutObj.Announce(sessionPort, aboutData);
if (ER_OK != status) {
printf("AboutObj Announce failed (%s)\n", QCC_StatusText(status));
exit(1);
}
The ObjectDesciprition part of the announced signal is found automatically by
introspectin the the BusObjects
that were registered with the BusAttachment
.
Any time a new interface is added or the AboutData is changed the Announce
member function should be called again.
Sample code for receiving an Announce
signal
Code that receives an Announce
signal will need to create, start, and connect
a BusAttachment
the same as the code that sent the Announce
signal. The
application that receives the Announce
signal does not need to bind a session
port. See create a BusAttachment
create an AboutListener
The AboutListener interface is responsible for responding to Announce
signals.
class MyAboutListener : public AboutListener {
void Announced(const char* busName, uint16_t version, SessionPort port,
const MsgArg& objectDescriptionArg, const MsgArg& aboutDataArg) {
// Place code here to handle Announce signal.
}
};
The AboutListener
is called by the AllJoyn routing node when an Announce
signal is found. The Announced
call back contains all the information
contained in the received Announce
signal as well as the unique BusName of the
BusAttachment
that emitted the Announce
signal. This information can be used
to form a session with the remote device; and Make a ProxyBus
object based on
the interfaces reported in the objectDescriptionArg
.
Register the new AboutListener
and call WhoImplements
MyAboutListener aboutListener;
bus.RegisterAboutListener(aboutListener);
const char* interfaces[] = { INTERFACE_NAME };
status = bus.WhoImplements(interfaces, sizeof(interfaces) / sizeof(interfaces[0]));
if (ER_OK != status) {
printf("WhoImplements call FAILED with status %s\n", QCC_StatusText(status));
exit(1);
}
Although it is possible to register multiple AboutListener
s it is unlikely
that a program will need more than one AboutListener
.
The WhoImplements
member function
The WhoImplements
member function is used to declare your interest in one or
more specific interfaces. If a remote device is announcing the interface(s)
then all Registered AboutListeners
will be called.
For example, if you need both com.example.Audio
and
com.example.Video
interfaces then do the following.
RegisterAboutListener once:
const char* interfaces[] = {"com.example.Audio", "com.example.Video"};
RegisterAboutListener(aboutListener);
WhoImplements(interfaces, sizeof(interfaces) / sizeof(interfaces[0]));
If the AboutListener should be called if com.example.Audio
or
com.example.Video
interfaces are found then call WhoImplements
multiple
times:
RegisterAboutListener(aboutListener);
const char* audioInterface[] = {"com.example.Audio"};
WhoImplements(audioInterface, sizeof(audioInterface) / sizeof(audioInterface[0]));
const char* videoInterface[] = {"com.example.Video"};
WhoImplements(videoInterface, sizeof(videoInterface) / sizeof(videoInterface[0]));
The interface name may be a prefix followed by a *
. Using this, the example
where we are interested in com.example.Audio
or com.example.Video
interfaces could be written as:
const char* exampleInterface[] = {"com.example.*"};
RegisterAboutListener(aboutListener);
WhoImplements(exampleInterface, sizeof(exampleInterface) / sizeof(exampleInterface[0]));
The AboutListener will receive any announcement that implements an interface
beginning with the com.example.
name.
It is the AboutListeners responsibility to parse through the reported interfaces
to figure out what should be done in response to the Announce
signal.
Calls to WhoImplements is ref counted. If WhoImplements is called with the same list of interfaces multiple times then CancelWhoImplements must also be called multiple times with the same list of interfaces.
Specifying NULL for the implementsInterfaces
parameter is allowed, however, it
could have significant impact on network performance and should be avoided
unless all announcements are needed.
Add an AboutIcon (optional)
An application that sends an Announce
signal can be extended to broadcast a
device icon using an instance of the AboutIconObj
class.
Provision for the Icon content and URL
An Icon is published directly as a byte array or a reference URL, and is provisioned as follows:
Create an icon using a byte array. An Icon size of 72 pixels x 72 pixels is recommended.
uint8_t aboutIconContent[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D /* Add relevant data here */ };
AboutIcon icon;
status = icon.SetContent("image/png", aboutIconContent, sizeof(aboutIconContent) / sizeof(aboutIconContent[0]));
Create an icon using a URL.
AboutIcon icon;
status = icon.SetUrl("image/png", "http://www.example.com");
As long as the MimeType of the Url and the icon content are the same. Both the Url and icon content can be set.
AboutIconObj
The AboutIconObj
will create and register a BusObject
to handle remote
method calls made on the org.alljoyn.Icon
interface. The AboutIconObj is
announced by default. Applications interested in the org.alljoyn.Icon
interface can call WhoImplements(org.alljoyn.Icon
) to find applications
that broadcast device icon information.
Announce the org.alljoyn.Icon
interface:
AboutIconObj aboutIconObj(bus, icon);
aboutObj.Announce(port, aboutData);
Discover the org.alljoyn.Icon interface
bus.WhoImplements(org::alljoyn::Icon::InterfaceName);
Using Ping to determine presence
The BusAttachment
Ping
member function can be used to determine
if a device is responsive. Contents of an Announce signal can
be stale so it is recommended to ping the device to see if it
is still present and responsive before attempting to form a connection.
NOTE: The BusAttachment::Ping
member function makes a bus call. If Ping
is called inside an AllJoyn callback, BusAttachment::EnableConcurrentCallbacks
must be called first.
// when pinging a remote bus wait a max of 5 seconds
#define PING_WAIT_TIME 5000
bus.EnableConcurrentCallbacks();
QStatus status = bus.Ping(busName.c_str(), PING_WAIT_TIME);
if( ER_OK == status) {
...
}
Request non-announced data
If there is a need to request information that is not contained in the announcement, perform the following steps.
- Join the session
Create a session with the application by calling BusAttachment::JoinSession
.
NOTE: The variables name and port are obtained from the AboutListener::Announced member function.
cpp
SessionId sessionId;
SessionOpts opts(SessionOpts::TRAFFIC_MESSAGES, false,
SessionOpts::PROXIMITY_ANY, TRANSPORT_ANY);
QStatus status = bus.JoinSession(name, port, NULL, sessionId, opts);
if (status == ER_OK) {
printf("JoinSession SUCCESS (Session id=%d)", sessionId);
} else {
printf("JoinSession failed");
}
- Create an
AboutProxy
Generate an About ProxyBusObject by passing the local BusAttachment
, the
name of the remote BusAttachment
, and the SessionId
obtained from the
BusAttachment::JoinSession
call.
cpp
AboutProxy aboutProxy(bus, busName, sessionId);
MsgArg arg;
status = aboutProxy.GetAboutData("", arg);
if(ER_OK != status) {
//handle error
}
- Create
AboutIconProxy
(optional)
Generate an Icon ProxyBusObject by passing the local BusAttachment
, the
name of the remote BusAttachment
, and the SessionId
obtained from the
BusAttachment::JoinSession
call.
```cpp AboutIconProxy aiProxy(bus, busName, sessionId);
AboutIcon retIcon;
status = aiProxy.GetIcon(retIcon);
if(ER_OK != status) {
//handle error
}
// Get the Url
retIcon.url
// Get the content size
retIcon.contentSize
// Get a pointer to the icon content
retIcon.content
// Get the MimeType
retIcon.mimetype
``
<!--QUESTION FOR GEORGE: Need to resolve TODOs-->
<!--TODO add section on adding user defined values to AboutData -->
<!--TODO add section on Creating child AboutData implementation -->
<!--TODO add section on Making an AboutDataListener from legacy PropertyStore -->
<!--TODO add section on run time adding and removing BusObjects using
BusObject::SetAnnouceFlag` -->