We've launched our new site at www.openlighting.org. This wiki will remain and be updated with more technical information.
OLA Client API
From wiki.openlighting.org
Contents
Warning: This documentation may be out of date. If in doubt check the code in the ola-examples package.
OLA provides a C++ client library which allows other applications to send/receive DMX as well as control the OLA server. When writing a client application, you have a choice between three client APIs.
Different Client APIs
If you want to:
- Only send DMX
- Use StreamingClient.h . This sets up a unidirectional channel between the client and the OLA server.
- Do any of the following send DMX, receive DMX, patch ports, control RDM devices
- Use the OlaCallbackClient which can be setup using the OlaCallbackClientWrapper
- Do any of the following send DMX, receive DMX, patch ports, control RDM devices. Deprecated
- Use the OlaClient which can be setup using the SimpleClient
The difference between OlaClient and OlaCallbackClient is how the response to the method calls is provided.
OlaClient
The OlaClient class provides a SetObserver() method which takes an object of type OlaClientObserver. When calls to the OlaClient object complete, the appropriate method is call on the OlaClientObserver object. The following diagrams attempts to show this (the red parts are the bit the client application provides).
class Observer: public ola::OlaClientObserver { public Observer() {}: void ForceRDMDiscoveryComplete(unsigned int universe, const string &error) { // something here } }; Observer observer(); unsigned int universe = 1; client->SetObserver(&observer); client->ForceDiscovery(universe);
OlaCallbackClient
On the other hand, the OlaCallbackClient takes a Callback object whenever a method is called.
void ForceRDMDiscoveryComplete(unsigned int universe, string &error) { // fill this in } unsigned int universe = 1; client->ForceDiscovery( universe, ola::NewSingleCallback(&ForceRDMDiscoveryComplete, universe));
The Callbacks used in OlaCallbackClient are much more flexible than the Observer model in OlaClient. It's recommended that all new code use OlaCallbackClient.
Example I: Sending an DMX update using the StreamingClient
The simplest interface to OLA is the StreamingClient which just allows an application to send DMX data to the OLA server
#include <stdlib.h> #include <ola/DmxBuffer.h> #include <ola/Logging.h> #include <ola/StreamingClient.h> #include <iostream> using std::cout; using std::endl; int main(int argc, char *argv[]) { unsigned int universe = 1; // universe to use for sending data unsigned int i; // turn on OLA logging ola::InitLogging(ola::OLA_LOG_WARN, ola::OLA_LOG_STDERR); // Create a new DmxBuffer to hold the data ola::DmxBuffer buffer; // set all channels to 0 buffer.Blackout(); // create a new client and set the Error Closure ola::StreamingClient ola_client; // Setup the client, this connects to the server if (!ola_client.Setup()) { cout << "Setup failed" << endl; exit(1); } // send the data to the ola server for (i = 0; i < 100; i++) { buffer.SetChannel(0, i); if (!ola_client.SendDmx(universe, buffer)) { cout << "Send DMX failed" << endl; exit(1); } usleep(20000); // sleep for 20ms between updates } // close the connection ola_client.Stop(); return 0; }
Note there isn't any need to poll for updates from the server in this case because the communication is all one way (client -> server). This also means that you can't receive DMX data or modify state on the server (like port patching and device parameters) when using the StreamingClient.
Also take note of the call to usleep(). Without this you're likely to fill the network socket buffer at which point the client code will detect an error and close the socket (you may need to increase the number of loop iterations to trigger this).
Example II: Sending an DMX update using the SimpleClient
This example simply connects to the OLA Server and sends one DMX update just like the example above but using the SimpleClient. It's about as basic as you can get because it requires no callbacks and exits immediately. We use two classes in this example, ola::SimpleClient, and ola::OlaClient.
#include <ola/DmxBuffer.h> #include <ola/Logging.h> #include <ola/SimpleClient.h> int main() { ola::InitLogging(ola::OLA_LOG_WARN, ola::OLA_LOG_STDERR); ola::DmxBuffer buffer; buffer.Blackout(); // some dummy dmx data buffer.SetChannel(0, 255); ola::SimpleClient simple_client; if (!simple_client.Setup()) //setup failed return -1; // Get the underlying OlaClient object ola::OlaClient *client = simple_client.GetClient(); // Send the DMX data client->SendDmx(1, buffer); }
The main class used to communicate with the LLA Server is OlaClient. For ease of use, the SimpleClient class sets up OlaClient with a connection to an LLA Server running on the localhost:LLA_DEFAULT_PORT. You can call GetClient() on a SimpleClient instance to get a pointer to the underlying OlaClient.
Example III: Sending multiple updates
The previous example only sent a single DMX update before quitting. This next example adds a timeout which sends DMX every 50ms. This introduces a new class SelectServer which is used for registering timeouts.
#include <ola/DmxBuffer.h> #include <ola/network/SelectServer.h> #include <ola/Logging.h> #include <ola/SimpleClient.h> #include <ola/Closure.h> // Maximum value of a dmx channel static const unsigned int MAX_DMX = 255; // How often to send updates static const unsigned int TIMEOUT_MS = 50; class DmxTimeout { public: DmxTimeout(ola::OlaClient *client): m_tick(0), m_client(client) { buffer.Blackout(); m_buffer.SetChannel(0, MAX_DMX); } // Called on timeout int SendDmx() { m_buffer.SetChannel(1, m_tick) m_buffer.SetChannel(2, MAX_DMX - m_tick) m_client->SendDmx(1, buffer); m_tick++; m_tick %= MAX_DMX + 1; // we must return 0 else we get canceled return 0; } private: unsigned int m_tick; ola::DmxBuffer m_buffer; ola::OlaClient *m_client; }; int main() { ola::InitLogging(ola::OLA_LOG_WARN, ola::OLA_LOG_STDERR); ola::SimpleClient simple_client; if (!simple_client.Setup()) return -1; // Create a timeout and register it DmxTimeout timeout(simple_client.GetClient()); ola::network::SelectServer ss = simple_client.GetSelectServer(); ss->RegisterTimeout(TIMEOUT_MS, ola::NewClosure(&timeout, &DmxTimeout::SendDmx), true); // we want this to repeat // Start the main loop ss->Run(); }
In this example we create DmxTimeout class whose SendDmx() method is called every time the timer expires.
The other important part here is the SelectServer. As well as the RegisterTimeout method we've used above, this can also be used to register sockets so we can respond to network activity. The Run() method starts the main event processing loop which will halt if an error occurs or Terminate() is called.
Example IV: Receiving DMX data
The third example shows how to listen and respond to event from the LLA server.
#include <ola/Logging.h> #include <ola/SimpleClient.h> static const unsigned int UNIVERSE = 1; class OurObserver: public ola::OlaClientObserver { public: // Called when new DMX values arrive void NewDmx(unsigned int universe, const DmxBuffer &data, const std::string &error) { OLA_INFO << "Received " << (int) data.Size() << " channels for universe " << (int) universe; } }; int main() { ola::InitLogging(ola::OLA_LOG_INFO, ola::OLA_LOG_STDERR); ola::SimpleClient simple_client; OurObserver observer; if (!simple_client.Setup()) return -1; ola::OlaClient *client = simple_client.GetClient(); // Set the observer and register our interest in this universe client->SetObserver(&observer); client->RegisterUniverse(UNIVERSE, ola::REGISTER); simple_client.GetSelectServer()->Run(); }
Here we inherit from the OlaClientObserver class and override the methods we're interested in receiving notification for.
In the main function we set the observer object and register our interest in a universe.
Example V: More complex client
The above is all well and good but what if the main application has it's own event processing loop? An example of this is a GTK/Glib application which uses GMainLoop.
On the other hand, what if you're not connecting to the LLA Server over TCP? Sometimes it may be desirable to embed the LLA server within the main application.
Bypassing the SimpleClient and using OlaClient directly addresses both these problems.
#include <ola/OlaClient.h> #include <ola/select_server/SelectServer.h> #include <ola/select_server/Socket.h> using ola::select_server::PipeSocket; int main() { // Create the select server ola::select_server::SelectServer ss; // Create the pipe socket to talk to the server on PipeSocket *pipe_socket = new PipeSocket(); if (!pipe_socket->Init()) return -1; // Remember to add this socket to the SelectServer ss.AddSocket(pipe_socket); // Setup the OlaClient ola::OlaClient client(pipe_socket); if (!client.Setup()) return -1; // At this point the client is setup. We then need to setup the LLAServer // ... // Once that is done we add the pipe as a new connection ola_server->NewConnection(m_pipe_socket->OppositeEnd()); }
This example shows how we can create our own instance of a ConnectedSocket (PipeSocket is a subclass of ConnectedSocket) and pass it to the OlaClient to use. This code is very similar to what SimpleClient does under the hood.