Open Lighting Architecture  Latest Git
Advanced C++ Client API

Table of Contents

Advanced C++ Client API Tutorial

Overview

This page covers some of the more advanced aspects of the OLA C++ Client API including using callbacks, handling a server-side shutdown and running the client in a separate thread. For an introduction on using the OLA C++ Client see C++ DMX Client API Tutorial.

Using Callbacks

Almost all of the ola::client::OlaClient methods take a Callback object. Each callback is executed when the server responds with the results of the method invocation.

Lets look at the ola::client::OlaClient::FetchPluginList method. This method is used to fetch a list of available plugins from the olad server. The client may choose to present the list of plugins to the user so they know what Devices / Protocols are supported.

The code below calls FetchPluginList() and then waits for the results from the olad server. When the list of plugins is received, the name of each plugin is printed to stdout and the program exits.

#include <ola/io/SelectServer.h>
#include <ola/Logging.h>
#include <ola/Callback.h>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
// Called when plugin information is available.
void ShowPluginList(ola::io::SelectServer *ss,
const ola::client::Result &result,
const vector<ola::client::OlaPlugin> &plugins) {
if (!result.Success()) {
std::cerr << result.Error() << endl;
} else {
vector<ola::client::OlaPlugin>::const_iterator iter = plugins.begin();
for (; iter != plugins.end(); ++iter) {
cout << "Plugin: " << iter->Name() << endl;
}
}
ss->Terminate(); // terminate the program.
}
int main(int, char *[]) {
if (!wrapper.Setup()) {
std::cerr << "Setup failed" << endl;
exit(1);
}
// Fetch the list of plugins, a pointer to the SelectServer is passed to the
// callback so we can use it to terminate the program.
wrapper.GetClient()->FetchPluginList(
ola::NewSingleCallback(ShowPluginList, ss));
// Start the main loop
ss->Run();
}

Handling Server Shutdown.

To write a robust program, you'll need to handle the case of olad shutting down. This can be done by setting a callback to be executed when the connection to the olad server is closed.

The following example builds on the OlaClient TX example. Instead of exiting after 100 DMX frames have been sent, the program runs until the connection to olad is closed.

#include <stdint.h>
#include <ola/DmxBuffer.h>
#include <ola/io/SelectServer.h>
#include <ola/Logging.h>
#include <ola/Callback.h>
using std::cout;
using std::endl;
// Called when the connection to olad is closed.
void ConnectionClosed(ola::io::SelectServer *ss) {
std::cerr << "Connection to olad was closed" << endl;
ss->Terminate(); // terminate the program.
}
bool SendData(ola::client::OlaClientWrapper *wrapper) {
static unsigned int universe = 1;
static uint8_t i = 0;
buffer.Blackout();
buffer.SetChannel(0, i++);
wrapper->GetClient()->SendDMX(universe, buffer, ola::client::SendDMXArgs());
return true;
}
int main(int, char *[]) {
if (!wrapper.Setup()) {
std::cerr << "Setup failed" << endl;
exit(1);
}
// Create a timeout and register it with the SelectServer
ss->RegisterRepeatingTimeout(25, ola::NewCallback(&SendData, &wrapper));
// Register the on-close handler
wrapper.GetClient()->SetCloseHandler(
ola::NewSingleCallback(ConnectionClosed, ss));
// Start the main loop
ss->Run();
}

Instead of exiting, a better approach would be to try and reconnect to the olad server. Clients that do this should use a ola::BackOffPolicy to avoid spinning in a loop trying to reconnect.

Running the OlaClient within a thread.

Sometimes it can be difficult to integrate OLA's Event Driven programming model with the main event loop of your program. The OlaClient is not thread safe, so a workaround is to run a separate thread for the OlaClient.

We can use ola::io::SelectServer::Execute to run a callback on a SelectServer running in a different thread. This allows us to schedule the calls to OlaClient on the thread where the SelectServer is running.

#include <ola/io/SelectServer.h>
#include <ola/Logging.h>
#include <ola/thread/Thread.h>
#include <ola/Callback.h>
#include <vector>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#include <ola/win/CleanWindows.h>
#endif // _WIN32
using std::cout;
using std::endl;
using std::vector;
public:
bool Start() {
if (!m_wrapper.Setup()) {
return false;
}
}
void Stop() {
m_wrapper.GetSelectServer()->Terminate();
}
void FetchPluginList(ola::client::PluginListCallback *callback) {
m_wrapper.GetSelectServer()->Execute(
NewSingleCallback(this, &OlaThread::InternalFetchPluginList, callback));
}
ola::io::SelectServer* GetSelectServer() {
return m_wrapper.GetSelectServer();
}
protected:
void *Run() {
m_wrapper.GetSelectServer()->Run();
return NULL;
}
private:
void InternalFetchPluginList(ola::client::PluginListCallback *callback) {
m_wrapper.GetClient()->FetchPluginList(callback);
}
};
// Called when plugin information is available.
// This function is run from the OLA Thread, if you use variables in the main
// program then you'll need to add locking.
void ShowPluginList(ola::io::SelectServer *ss,
const ola::client::Result &result,
const vector<ola::client::OlaPlugin> &plugins) {
if (!result.Success()) {
std::cerr << result.Error() << endl;
} else {
vector<ola::client::OlaPlugin>::const_iterator iter = plugins.begin();
for (; iter != plugins.end(); ++iter) {
cout << "Plugin: " << iter->Name() << endl;
}
}
ss->Terminate(); // terminate the program.
}
int main(int, char *[]) {
OlaThread ola_thread;
if (!ola_thread.Start()) {
std::cerr << "Failed to start OLA thread" << endl;
exit(1);
}
// Control is returned to the main program here.
// To fetch a list of plugins
ola::io::SelectServer *ss = ola_thread.GetSelectServer();
ola_thread.FetchPluginList(
ola::NewSingleCallback(ShowPluginList, ss));
// The main program continues...
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif // _WIN32
// When it's time to exit, Stop the OLA thread.
ola_thread.Stop();
ola_thread.Join();
}

It's important to realize that the ShowPluginList function will be run from the OLA thread. If this function uses any variables from the main program (such as UI widgets), you must use locking, or some other thread synchronization technique.