Topic: RATC2 Client example code - C++
Hi all-
In preparation for the recent Infocomm trade-show I updated our demonstration iPhone app with new RATC2 client code. As it is a general RATC2 client written in C++ I thought others here might find it useful as an example of how to talk to RATC2 over TCP.
A few caveats of course:
1) No warranty. This is intended for demonstration and educational purposes only.
2) The error handling is very simplistic. To keep it simple I avoided the use of exceptions. All methods in the class return booleans to indicate the success or failure of each command. You are of course free to improve the error handling as suits your needs.
3) This was written primarily for, and tested primarily on, the iPhone. Consideration was made for Windows sockets APIs but little testing was done on Windows.
4) A basic knowledge of RATC/RATC2 and how it works with control aliases in NWare is assumed. If you are unfamiliar with RATC/RATC2 you should see the online help included with NWare.
That said, I think many will find this class useful 'as is' for basic RATC2 communication needs. The code is well commented but I'll add a few notes here.
There is only one top-level class: ratc2_client. This class abstracts away all socket communication and RATC2 protocol details. The user of the class need only supply the address and port of the server to connect. The socket is managed within the scope of the object. The connection will be closed and socket resource released when the object goes out of scope. A method on the class allows for the adjustment of socket communication timeout values. This value is a float representation of seconds and as such can represent sub-second values if necessary.
The class supports both change group as well individual control alias gets and sets. A sub-class of ratc2_client called 'ratc_control_value' abstracts away the need to either create or parse RATC2 commands. Objects of this sub-class are returned from all control gets and expected as input for all control sets. The ratc_control_value class allows direct access to the value and position member variables and includes constructors for creating objects suitable for either value or position set commands.
Here's a contrived example of how to use the ratc2_client class:
#include "ract2_client.h"
void example()
{
ratc2_client client;
//connect
if (!client.connect("10.0.0.1", 1632)) {
std::cout << "Failed to connect." << std::endl;
return;
}
//login
if (!client.logIn("defaultUser", "defaultPass")) {
std::cout << "Failed to login." << std::endl;
return;
}
//check for project name... this is optional but useful
std::string project;
if (!client.statusGet(project)) {
std::cout << "Failed to get project status." << std::endl;
return;
} else {
std::cout << "Connected to project: " << project << std::endl;
}
//set a control position to .5
ratc2_client::ratc_control_value knobPosition("someKnobAlias", 0.5);
if (!client.controlSet(knobPosition)) {
std::cout << "Failed to set someKnobAlias position. Giving up..." << std::endl;
return;
} else {
std::cout << "Set someKnobAlias to: 0.5 actual value now: " << knobPosition.position << std::endl;
}
//create a change group to monitor
client.changeGroupControlAdd("GroupName", "control1");
client.changeGroupControlAdd("GroupName", "control2");
client.changeGroupControlAdd("GroupName", "control3");
client.changeGroupControlAdd("GroupName", "control4");
//monitor the change group
ratc2_client::CONTROL_VALUE_LIST change_group_list;
while (true) {
if (!client.changeGroupGet("GroupName", change_group_list)) {
std::cout << "Connection failure? Network problems? Giving up..." << std::endl;
return;
}
//if we have control changes process them
if (change_group_list.size() > 0) {
ratc2_client::CONTROL_VALUE_LIST::iterator it;
for (it = change_group_list.begin(); it != change_group_list.end(); ++it) {
std::cout << "Control: " << it->name << " value: '" << it->value << "' position: " << it->position << std::endl;
}
}
//wait a while
sleep(250);
}
}
Here's the code itself:
ratc2_client.h
/*
* ratc_client.h
*
* Created by Frank Vernon on 8/30/08.
* Copyright 2008 Peavey Electronics. All rights reserved.
*
*/
#ifndef _H_RATC2_CLIENT_H
#define _H_RATC2_CLIENT_H
#include <string>
#include <list>
#pragma mark ratc2_client
//Simple RATC2 client
// Note: Error handling is very simplistic. All routines return true/false
// on success/failure but no reasons are supplied for the causes of failure
class ratc2_client {
public:
//struct representing value/position for a named control alias
struct ratc_control_value {
std::string name;
std::string value;
float position;
bool set_position;
ratc_control_value();
ratc_control_value(const char * in_ratc_response);
ratc_control_value(const char * in_name, const char * in_value);
ratc_control_value(const char * in_name, float in_position);
};
//a list of control values
typedef std::list<ratc_control_value> CONTROL_VALUE_LIST;
//construct/destruct
ratc2_client();
virtual ~ratc2_client();
//connectioin management
bool connect(const char * in_address, int in_port);
void disconnect();
//login to RATC server
bool logIn(const char * in_user, const char * in_pass);
//status... returns running project name
bool statusGet(std::string & out_project);
//get present value of a control alias
bool controlGet(const char * in_control_alias, ratc_control_value & out_value);
// wrapper routine to set value/position on a control alias using a ratc_control_value object
// on return, if result is true, the io_value is updated to the actual value of the control.
// NOTE: controls may be bound by ranges or rounded to fit allowable values thus you may want to update your UI
// to indicate the actual value after this value returns.
// NOTE: There is no corresponding controlPositionSet. A position set is used if the 'in_position'
// version of the constructor was used to create the io_value object.
bool controlSet(ratc_control_value & io_value);
//change group add/remove/clear
bool changeGroupControlAdd(const char * in_group_name, const char * in_control_alias);
bool changeGroupControlRemove(const char * in_group_name, const char * in_control_alias);
bool changeGroupClear(const char * in_group_name);
//change group get
// if returns false there has been a communications problem, you will likely want to try to reconnect
bool changeGroupGet(const char * in_group_name, CONTROL_VALUE_LIST & out_responses);
//adjust the read timeout if required... class defaults to -1, no timeout
void set_read_timeout(float in_timeout_secs);
float get_read_timeout();
protected:
int client_socket;
char read_buffer[256];
std::string result_buffer;
float read_timeout;
//primitive methods for reading/writting RATC commands on the socket
// you would normally use the public utility routines
bool read_response(std::string & out_response, float in_timeout_secs = -1);
bool send_command(const std::string & in_command);
bool internal_controlPositionSet(ratc_control_value & io_value);
bool internal_controlSet(ratc_control_value & io_value);
};
#endif //_H_RATC2_CLIENT_H
ratc2_client.cpp
/*
* ratc2_client.cpp
*
* Created by Frank Vernon on 8/30/08.
* Copyright 2008 Peavey Electronics. All rights reserved.
*
*/
#include "ratc2_client.h"
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <math.h>
#pragma mark ratc_control_value
ratc2_client::ratc_control_value::ratc_control_value()
:position(0), set_position(false)
{
}
//down and dirty parse of ratc command response
//example response: valueIs "level1" 0 0.000
// where: "level1" is the alias name, 0 is the value, 0.000 is the position
ratc2_client::ratc_control_value::ratc_control_value(const char * in_ratc_response)
:set_position(false), position(0.0)
{
const char * curr_pos, * next_pos;
curr_pos = ::strstr(in_ratc_response, " \""); //first quote
if (curr_pos) {
curr_pos += 2; //step over leading space and quote
next_pos = ::strstr(curr_pos, "\" "); //second quote
if (next_pos) {
name.assign(curr_pos, next_pos - curr_pos); //grab name between quotes
curr_pos = next_pos + 2; //step over trailing quote and space
next_pos = ::strrchr(curr_pos, ' '); //find end of value
if (next_pos) {
value.assign(curr_pos, next_pos-curr_pos);
curr_pos = next_pos + 1; //move to head of position
position = ::atof(curr_pos);
}
}
}
}
//create a 'value' type
ratc2_client::ratc_control_value::ratc_control_value(const char * in_name, const char * in_value)
:name(in_name), value(in_value), position(0), set_position(false)
{
}
//create a 'position' type
ratc2_client::ratc_control_value::ratc_control_value(const char * in_name, float in_position)
:name(in_name), value(""), position(in_position), set_position(true)
{
}
#pragma mark ratc2_client - public
ratc2_client::ratc2_client()
:client_socket(NULL), read_timeout(-1)
{
}
ratc2_client::~ratc2_client()
{
disconnect();
}
bool
ratc2_client::connect(const char * in_address, int in_port)
{
disconnect();
//create socket
if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
client_socket = NULL;
return false;
}
//create inet address structure
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(in_port);
inet_aton(in_address, &server_address.sin_addr);
memset(server_address.sin_zero, '\0', sizeof(server_address.sin_zero));
//connect
if (::connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
close(client_socket);
client_socket = NULL;
return false;
}
//eat useless welcome message from ratc server
send_command("\r");
std::string response;
read_response(response, read_timeout);
return true;
}
void
ratc2_client::disconnect()
{
if (client_socket) {
::shutdown(client_socket, SHUT_RDWR);
::close(client_socket);
client_socket = NULL;
}
}
//li logIn username password : login to the ratc server
bool
ratc2_client::logIn(const char * in_user, const char * in_pass)
{
char temp[512];
int result = snprintf(temp, sizeof(temp), "li %s %s\r", in_user, in_pass);
if (result > sizeof(temp)) {
return false;
}
send_command(temp);
std::string data;
read_response(data, read_timeout);
return data.find("loggedIn") != std::string::npos;
}
//sg statusGet : return name of running project, false if not running
bool
ratc2_client::statusGet(std::string & out_project)
{
out_project.clear();
send_command("sg\r");
std::string data;
read_response(data, read_timeout);
if (data.find("statusIs") != std::string::npos) {
std::string::size_type start = data.find(" \"");
if (start != std::string::npos) {
start += 2;
std::string::size_type end = data.find("\"", start);
if (end != std::string::npos) {
out_project = data.substr(start, end - start);
}
}
}
return !out_project.empty();
}
//cg controlGet alias : get value/position of a control alias
bool
ratc2_client::controlGet(const char * in_control_alias, ratc_control_value & out_value)
{
char temp[512];
int result = snprintf(temp, sizeof(temp), "cg %s\r", in_control_alias);
if (result > sizeof(temp)) {
return false;
}
send_command(temp);
std::string data;
read_response(data, read_timeout);
bool status = data.find("valueIs") != std::string::npos;
if (status) {
out_value = ratc_control_value(data.c_str());
}
return status;
}
// wrapper routine to set value/position on a control alias using a ratc_control_value object
// on return, if result is true, the io_value is updated to the actual value of the control
// controls may be bound by ranges or rounded to fit allowable values
bool
ratc2_client::controlSet(ratc_control_value & io_value)
{
if (io_value.set_position) {
return internal_controlPositionSet(io_value);
} else {
return internal_controlSet(io_value);
}
}
//cgca changeGroupControlAdd [group] control : add a Control to a Change Group
bool
ratc2_client::changeGroupControlAdd(const char * in_group_name, const char * in_control_alias)
{
char temp[512];
int result = snprintf(temp, sizeof(temp), "cgca %s %s\r", in_group_name, in_control_alias);
if (result > sizeof(temp)) {
return false;
}
send_command(temp);
std::string data;
read_response(data, read_timeout);
bool status = data.find("changeGroupControlAdded") != std::string::npos;
return status;
}
//cgg changeGroupGet [group] : get changed values from a Change Group
// if returns false there has been a communications problem, you will likely want to try to reconnect
bool
ratc2_client::changeGroupGet(const char * in_group, CONTROL_VALUE_LIST & out_responses)
{
out_responses.clear();
char temp[512];
int result = snprintf(temp, sizeof(temp), "cgg %s\r", in_group);
if (result > sizeof(temp)) {
return false;
}
send_command(temp);
std::string data;
read_response(data, read_timeout);
//check for expected response in response buffer
result = snprintf(temp, sizeof(temp), "changeGroupChanges \"%s\" ", in_group);
if (result > sizeof(temp)) {
return false;
}
std::string::size_type pos = data.find(temp);
if (pos == std::string::npos) {
return false;
}
//get number of responses to expect and then read them all
uint32_t count = atol(data.substr(pos + result).c_str());
for (int i = 0; i < count; ++i) {
if (!read_response(data, read_timeout)) {
return false;
}
//expect 'valueIs' at head of each response
if (data.find("valueIs") == 0) {
out_responses.push_back(ratc_control_value(data.c_str()));
}
}
return true;
}
void
ratc2_client::set_read_timeout(float in_timeout_secs)
{
read_timeout = in_timeout_secs;
}
float
ratc2_client::get_read_timeout()
{
return read_timeout;
}
#pragma mark ratc2_client - protected
//Internal routine to read from the socket and return the next correctly formed
// response from the ratc server.
bool
ratc2_client::read_response(std::string & out_response, float in_timeout_secs)
{
if (!client_socket) {
return false;
}
out_response.clear();
// Initialize time out struct in case we need it
double int_part, fract_part;
fract_part = ::modf(in_timeout_secs, &int_part);
struct timeval tv;
tv.tv_sec = int_part;
tv.tv_usec = fract_part * 1000000.0;
//read until we have line ending in the byte stream or until we timeout
while (true) {
//check for result already in local buffer
std::string::size_type pos = result_buffer.find("\r\n");
if (pos != std::string::npos) {
out_response = result_buffer.substr(0, pos); //return response w/o CRLF
result_buffer.erase(0, pos + 2); //remove response including CRLF
break;
}
//do select for timeout if necessary
if (in_timeout_secs >= 0) {
// Initialize the fd set
fd_set readset;
FD_ZERO(&readset);
FD_SET(client_socket, &readset);
//do select
//Note: Windows select() is archaic... it does not return the time remaining in the tv struct
// so you might have long timeouts on Windows here under some scenarios. On 'real' OSes
// the timeout will count down across multiple calls to select so the total elapsed time
// here will not exceed the value passed in by the user.
int result = select(client_socket+1, &readset, NULL, NULL, in_timeout_secs >= 0 ? &tv : NULL);
if (result < 0 || !FD_ISSET(client_socket, &readset)) {
return false;
}
}
//read available data
int bytes_read = recv(client_socket, read_buffer, sizeof(read_buffer), 0);
if (bytes_read == -1) {
return false;
}
//append data to our local buffer and try again
result_buffer.append(read_buffer, bytes_read);
}
return true;
}
//Internal routine to write to the socket
bool
ratc2_client::send_command(const std::string & in_command)
{
if (!client_socket) {
return false;
}
std::string::size_type command_len = in_command.length();
const char * data_ptr = in_command.c_str();
int data_sent = 0;
while (command_len) {
int sent = ::send(client_socket, data_ptr + data_sent, command_len, 0);
if (sent == -1) {
return false;
}
data_sent += sent;
command_len -= sent;
}
return true;
}
//cps controlPositionSet control position : set a Control's position (0.00-1.00)
bool
ratc2_client::internal_controlPositionSet(ratc_control_value & io_value)
{
char temp[512];
int result = snprintf(temp, sizeof(temp), "cps \"%s\" %g\r", io_value.name.c_str(), io_value.position);
if (result > sizeof(temp)) {
return false;
}
send_command(std::string(temp));
std::string data;
read_response(data, read_timeout);
if (data.find("valueIs") != std::string::npos) {
io_value = ratc_control_value(data.c_str());
return true;
} else {
return false;
}
}
//cs controlSet control value : set a Control's value
bool
ratc2_client::internal_controlSet(ratc_control_value & io_value)
{
char temp[512];
int result = snprintf(temp, sizeof(temp), "cs \"%s\" %s\r", io_value.name.c_str(), io_value.value.c_str());
if (result > sizeof(temp)) {
return false;
}
send_command(std::string(temp));
std::string data;
read_response(data, read_timeout);
if (data.find("valueIs") != std::string::npos) {
io_value = ratc_control_value(data.c_str());
return true;
} else {
return false;
}
}
Last edited by fvernon (2009-07-07 19:49:18)