Home / System Design

RATC2 Client example code - C++

CommunityMartin AI Resolved
Started by fvernon · 16y ago · 12 views · 1 replies
16y ago

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 0) {
ratc2_client::CONTROL_VALUE_LIST::iterator it;
for (it = change_group_list.begin(); it != change_group_list.end(); ++it) {
std::cout name value position
#include

#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 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

#include
#include
#include

#include

#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 = 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 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;
}
}

16y ago

fvernon wrote:
//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.
A 'real' OS? Like Vista? I believe POSIX allows select() to leave timeout alone, or to modify it as you note. Unfortunately portable code cannot rely on that feature.
Kudos for actually implementing a line reassembly buffer, a run into a lot of people to don't realize that the stream nature of TCP offers no guarantee that a single read operation will return a full block/line/record/whatever. I like to test the code by limiting read() to a single byte.

Log in to reply to this topic.