Rivendellaudio/ripcd/sasusi.cpp
Fred Gleason 9daedd7f19 2020-02-16 Fred Gleason <fredg@paravelsystems.com>
* Removed Q3Socket dependency from the SAS USI switcher driver.
2020-02-16 14:36:35 -05:00

646 lines
16 KiB
C++

// sasusi.cpp
//
// A Rivendell switcher driver for the SAS USI Protocol
//
// (C) Copyright 2002-2020 Fred Gleason <fredg@paravelsystems.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
#include <stdlib.h>
#include <syslog.h>
#include <rdapplication.h>
#include <rddb.h>
#include <rdescape_string.h>
#include "globals.h"
#include "sasusi.h"
SasUsi::SasUsi(RDMatrix *matrix,QObject *parent)
: Switcher(matrix,parent)
{
QString sql;
RDSqlQuery *q;
RDTty *tty;
sas_matrix=matrix->matrix();
sas_ptr=0;
//
// Get Matrix Parameters
//
sas_porttype=matrix->portType(RDMatrix::Primary);
sas_ipaddress=matrix->ipAddress(RDMatrix::Primary);
sas_ipport=matrix->ipPort(RDMatrix::Primary);
sas_inputs=matrix->inputs();
sas_outputs=matrix->outputs();
sas_gpis=matrix->gpis();
sas_gpos=matrix->gpos();
sas_start_cart=matrix->startCart(RDMatrix::Primary);
sas_stop_cart=matrix->stopCart(RDMatrix::Primary);
//
// Load Switch Table
//
sql=QString("select ")+
"ENGINE_NUM,"+ // 00
"DEVICE_NUM,"+ // 01
"RELAY_NUM "+ // 02
"from VGUEST_RESOURCES where "+
"(STATION_NAME=\""+RDEscapeString(rda->config()->stationName())+"\")&&"+
QString().sprintf("(MATRIX_NUM=%d) ",matrix->matrix())+
"order by NUMBER";
q=new RDSqlQuery(sql);
while(q->next()) {
sas_console_numbers.push_back(q->value(0).toInt());
sas_source_numbers.push_back(q->value(1).toInt());
sas_relay_numbers.push_back(q->value(2).toInt());
}
delete q;
//
// Reconnection Timer
//
sas_reconnect_timer=new QTimer(this);
connect(sas_reconnect_timer,SIGNAL(timeout()),this,SLOT(ipConnect()));
//
// Initialize the connection
//
switch(sas_porttype) {
case RDMatrix::TtyPort:
tty=new RDTty(rda->station()->name(),matrix->port(RDMatrix::Primary));
sas_device=new RDTTYDevice();
if(tty->active()) {
sas_device->setName(tty->port());
sas_device->setSpeed(tty->baudRate());
sas_device->setWordLength(tty->dataBits());
sas_device->setParity(tty->parity());
sas_device->open(QIODevice::Unbuffered|QIODevice::WriteOnly);
}
delete tty;
case RDMatrix::TcpPort:
sas_socket=new QTcpSocket(this);
connect(sas_socket,SIGNAL(connected()),this,SLOT(connectedData()));
connect(sas_socket,SIGNAL(disconnected()),
this,SLOT(connectionClosedData()));
connect(sas_socket,SIGNAL(readyRead()),
this,SLOT(readyReadData()));
connect(sas_socket,SIGNAL(error(QAbstractSocket::SocketError)),
this,SLOT(errorData(Q)));
ipConnect();
break;
case RDMatrix::NoPort:
break;
}
}
RDMatrix::Type SasUsi::type()
{
return RDMatrix::SasUsi;
}
unsigned SasUsi::gpiQuantity()
{
return sas_gpis;
}
unsigned SasUsi::gpoQuantity()
{
return sas_gpos;
}
bool SasUsi::primaryTtyActive()
{
return sas_porttype==RDMatrix::TtyPort;
}
bool SasUsi::secondaryTtyActive()
{
return false;
}
void SasUsi::processCommand(RDMacro *cmd)
{
char str[256];
char cmd_byte;
QString label;
switch(cmd->command()) {
case RDMacro::CL:
if((cmd->arg(1).toInt()<1)||
((cmd->arg(1).toInt()>256)&&(cmd->arg(1).toInt()!=999))||
(cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>sas_inputs)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
for(int i=3;i<(cmd->argQuantity()-1);i++) {
label+=(cmd->arg(i)+" ");
}
label+=cmd->arg(cmd->argQuantity()-1);
if(label.length()>8) {
label=label.left(8);
}
for(int i=label.length();i<8;i++) {
label+=" ";
}
snprintf(str,256,"%c21%03d%04d%s\x0D\x0A",26,
cmd->arg(1).toInt(),cmd->arg(2).toInt(),(const char *)label);
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::FS:
if((cmd->arg(1).toInt()<1)||(cmd->arg(1).toInt()>256)||
(sas_porttype!=RDMatrix::TcpPort)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
snprintf(str,256,"%c1%03d\x0D\x0A",0x13,cmd->arg(1).toInt());
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::SG:
if((cmd->arg(1).toInt()<1)||(cmd->arg(1).toInt()>sas_inputs)||
(cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>sas_outputs)||
(cmd->arg(3).toInt()<-1024)||(cmd->arg(3).toInt()>1024)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
snprintf(str,256,"%c00%04d%04d%04d00548\x0D\x0A",26,
cmd->arg(1).toInt(),cmd->arg(2).toInt(),
cmd->arg(3).toInt()+1024);
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::SX:
if((cmd->arg(1).toInt()<1)||(cmd->arg(1).toInt()>sas_inputs)||
(cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>sas_outputs)||
(cmd->arg(3).toInt()<-1024)||(cmd->arg(3).toInt()>1024)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
snprintf(str,256,"%c10%04d%04d%04d0010\x0D\x0A",26,
cmd->arg(1).toInt(),cmd->arg(2).toInt(),
cmd->arg(3).toInt()+1024);
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::SL:
if((cmd->arg(1).toInt()<1)||(cmd->arg(1).toInt()>sas_inputs)||
(cmd->arg(2).toInt()<-1024)||(cmd->arg(2).toInt()>1024)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
snprintf(str,256,"%c10%04d0000%04d0001\x0D\x0A",26,
cmd->arg(1).toInt(),cmd->arg(2).toInt()+1024);
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::SA:
if((cmd->arg(1).toInt()<1)||(cmd->arg(1).toInt()>sas_inputs)||
(cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>sas_outputs)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
snprintf(str,256,"%c00%04d%04d102400036\x0D\x0A",26,
cmd->arg(1).toInt(),cmd->arg(2).toInt());
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::SR:
if((cmd->arg(1).toInt()<1)||(cmd->arg(1).toInt()>sas_inputs)||
(cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>sas_outputs)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
snprintf(str,256,"%c00%04d%04d102400032\x0D\x0A",26,
cmd->arg(1).toInt(),cmd->arg(2).toInt());
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::ST:
if((cmd->arg(1).toInt()<0)||(cmd->arg(1).toInt()>sas_inputs)||
(cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>sas_outputs)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
snprintf(str,256,"%cT%04d%04d\x0D\x0A",5,
cmd->arg(1).toInt(),cmd->arg(2).toInt());
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
break;
case RDMacro::GO:
if((cmd->arg(1).lower()!="o")||
(cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>sas_gpos)) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
if(cmd->arg(4).toInt()==0) { // Latch
if(cmd->arg(3).toInt()==0) { // Off
cmd_byte=0x03;
}
else {
cmd_byte=0x02;
}
}
else {
if(cmd->arg(3).toInt()==0) {
cmd->acknowledge(false);
emit rmlEcho(cmd);
return;
}
cmd_byte=0x01;
}
if(cmd->arg(2).toUInt()<=sas_relay_numbers.size()) {
if(sas_relay_numbers[cmd->arg(2).toUInt()-1]>=0) {
snprintf(str,256,"\x05R%d%04d\x0D\x0A",cmd_byte,
sas_relay_numbers[cmd->arg(2).toUInt()-1]);
rda->syslog(LOG_DEBUG,"USI: %s",
(const char *)PrettifyCommand(str));
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
}
else {
if((sas_console_numbers[cmd->arg(2).toUInt()-1]>=0)&&
(sas_source_numbers[cmd->arg(2).toUInt()-1]>=0)) {
if(cmd->arg(3).toInt()==0) { // Off
cmd_byte=0;
}
else {
cmd_byte=1;
}
snprintf(str,256,"\x1A%s%d%03d%04d\x0D\x0A","20",cmd_byte,
sas_console_numbers[cmd->arg(2).toUInt()-1],
sas_source_numbers[cmd->arg(2).toUInt()-1]);
SendCommand(str);
cmd->acknowledge(true);
emit rmlEcho(cmd);
}
}
}
break;
default:
cmd->acknowledge(false);
emit rmlEcho(cmd);
break;
}
}
void SasUsi::ipConnect()
{
sas_socket->connectToHost(sas_ipaddress.toString(),sas_ipport);
}
void SasUsi::connectedData()
{
rda->syslog(LOG_INFO,
"connection to SasUsi device at %s:%d established",
(const char *)sas_ipaddress.toString().toUtf8(),
sas_ipport);
if(sas_start_cart>0) {
ExecuteMacroCart(sas_start_cart);
}
}
void SasUsi::connectionClosedData()
{
rda->syslog(LOG_WARNING,
"connection to SasUsi device at %s:%d closed unexpectedly, attempting reconnect",
(const char *)sas_ipaddress.toString().toUtf8(),
sas_ipport);
if(sas_stop_cart>0) {
ExecuteMacroCart(sas_stop_cart);
}
sas_reconnect_timer->start(SASUSI_RECONNECT_INTERVAL,true);
}
void SasUsi::readyReadData()
{
char buffer[256];
unsigned n;
while((n=sas_socket->readBlock(buffer,255))>0) {
buffer[n]=0;
for(unsigned i=0;i<n;i++) {
if(buffer[i]==10) { // End of line
sas_buffer[--sas_ptr]=0;
DispatchCommand();
sas_ptr=0;
}
else {
if(sas_ptr==SASUSI_MAX_LENGTH) { // Buffer overflow
sas_ptr=0;
}
sas_buffer[sas_ptr++]=buffer[i];
}
}
}
}
void SasUsi::errorData(QAbstractSocket::SocketError err)
{
switch(err) {
case QAbstractSocket::ConnectionRefusedError:
rda->syslog(LOG_WARNING,
"connection to SasUsi device at %s:%d refused, attempting reconnect",
(const char *)sas_ipaddress.toString().toUtf8(),
sas_ipport);
sas_reconnect_timer->start(SASUSI_RECONNECT_INTERVAL,true);
break;
case QAbstractSocket::HostNotFoundError:
rda->syslog(LOG_WARNING,
"error on connection to SasUsi device at %s:%d: Host Not Found",
(const char *)sas_ipaddress.toString().toUtf8(),
sas_ipport);
break;
default:
rda->syslog(LOG_WARNING,
"received network error %d on connection to SasUsi device at %s:%d",
(const char *)sas_ipaddress.toString().toUtf8(),
sas_ipport);
break;
}
}
void SasUsi::SendCommand(char *str)
{
rda->syslog(LOG_INFO,"sending USI cmd: %s",
(const char *)PrettifyCommand(str));
switch(sas_porttype) {
case RDMatrix::TtyPort:
sas_device->write(str,strlen(str));
break;
case RDMatrix::TcpPort:
sas_socket->writeBlock(str,strlen(str));
break;
case RDMatrix::NoPort:
break;
}
}
void SasUsi::DispatchCommand()
{
char buffer[SASUSI_MAX_LENGTH];
unsigned input;
unsigned output;
int line;
unsigned action;
int console;
int source;
bool state;
bool ok=false;
QString cmd;
QString label;
QString sql;
RDSqlQuery *q;
//LogLine(RDConfig::LogNotice,QString().sprintf("DISPATCHED: %s",(const char *)sas_buffer));
//
// Startup Sequence. Get the input and output lists. The response
// to the ^EI command lets us know when the lists are done.
//
if(QString("login sucessful")==(QString(sas_buffer).lower())) {
sprintf(buffer,"%cX9999\x0D\x0A",5); // Request Input List
SendCommand(buffer);
sprintf(buffer,"%cY9999\x0D\x0A",5); // Request Output List
SendCommand(buffer);
sprintf(buffer,"%cI0001\x0D\x0A",5); // Start Finished
SendCommand(buffer);
return;
}
//
// Work around the idiotic 'SAS READY' prompt
//
if(sas_buffer[0]==27) {
for(unsigned i=17;i<strlen(sas_buffer)+1;i++) {
sas_buffer[i-17]=sas_buffer[i];
}
}
rda->syslog(LOG_INFO,"received USI cmd: %s",
(const char *)PrettifyCommand(sas_buffer));
//
// Process Commands
//
switch(sas_buffer[0]) {
case 21: // Input Name [^U]
if(strlen(sas_buffer)<13) {
return;
}
label=sas_buffer+5;
sas_buffer[5]=0;
if(sscanf(sas_buffer+1,"%u",&input)!=1) {
return;
}
sql=QString("select NUMBER from INPUTS where ")+
"(STATION_NAME=\""+RDEscapeString(rda->station()->name())+"\")&&"+
QString().sprintf("(MATRIX=%d)&&",sas_matrix)+
QString().sprintf("(NUMBER=%d)",input);
q=new RDSqlQuery(sql);
if(q->first()) {
sql=QString("update INPUTS set ")+
"NAME=\""+RDEscapeString(label)+"\" where "+
"(STATION_NAME=\""+RDEscapeString(rda->station()->name())+"\")&&"+
QString().sprintf("(MATRIX=%d)&&",sas_matrix)+
QString().sprintf("(NUMBER=%d)",input);
}
else {
sql=QString("insert into INPUTS set ")+
"NAME=\""+RDEscapeString(label)+"\","+
"STATION_NAME=\""+RDEscapeString(rda->station()->name())+"\","+
QString().sprintf("MATRIX=%d,",sas_matrix)+
QString().sprintf("NUMBER=%d",input);
}
delete q;
q=new RDSqlQuery(sql);
delete q;
break;
case 22: // Output Name [^V]
if(strlen(sas_buffer)<13) {
return;
}
label=sas_buffer+5;
sas_buffer[5]=0;
if(sscanf(sas_buffer+1,"%u",&output)!=1) {
return;
}
sql=QString("select NUMBER from OUTPUTS where ")+
"(STATION_NAME=\""+RDEscapeString(rda->station()->name())+"\")&&"+
QString().sprintf("(MATRIX=%d)&&",sas_matrix)+
QString().sprintf("(NUMBER=%d)",output);
q=new RDSqlQuery(sql);
if(q->first()) {
sql=QString("update OUTPUTS set ")+
"NAME=\""+RDEscapeString(label)+"\" where "+
"(STATION_NAME=\""+RDEscapeString(rda->station()->name())+"\")&&"+
QString().sprintf("(MATRIX=%d)&&",sas_matrix)+
QString().sprintf("(NUMBER=%d)",output);
}
else {
sql=QString("insert into OUTPUTS set ")+
"NAME=\""+RDEscapeString(label)+"\","+
"STATION_NAME=\""+RDEscapeString(rda->station()->name())+"\","+
QString().sprintf("MATRIX=%d,",sas_matrix)+
QString().sprintf("NUMBER=%d",output);
}
delete q;
q=new RDSqlQuery(sql);
delete q;
break;
case 'M': // Console Module Action
if(strlen(sas_buffer)<9) {
return;
}
cmd=QString(sas_buffer);
console=cmd.mid(2,3).toInt(&ok);
if(!ok) {
return;
}
source=cmd.mid(5,4).toInt(&ok);
if(!ok) {
return;
}
for(unsigned i=0;i<sas_console_numbers.size();i++) {
if((console==sas_console_numbers[i])&&(source==sas_source_numbers[i])) {
action=cmd.mid(1,1).toUInt(&ok);
if(!ok) {
return;
}
switch(action) {
case 0: // Module OFF
emit gpiChanged(sas_matrix,i,false);
break;
case 1: // Module ON
emit gpiChanged(sas_matrix,i,true);
break;
case 2: // Cue OFF
break;
case 3: // Cue ON
break;
}
}
}
break;
case 'Z': // Opto/Relay Tally Update
if(strlen(sas_buffer)<9) {
return;
}
cmd=QString(sas_buffer);
line=cmd.mid(4,4).toInt(&ok);
if(!ok) {
return;
}
state=cmd.right(1).toInt(&ok);
if(!ok) {
return;
}
for(unsigned i=0;i<sas_relay_numbers.size();i++) {
if(line==sas_relay_numbers[i]) {
switch(cmd.mid(1,2).toInt()) {
case 0: // Opto
emit gpiChanged(sas_matrix,i,state);
break;
case 1: // Relay
emit gpoChanged(sas_matrix,i,state);
break;
}
}
}
break;
}
}
void SasUsi::ExecuteMacroCart(unsigned cartnum)
{
RDMacro rml;
rml.setRole(RDMacro::Cmd);
rml.setCommand(RDMacro::EX);
rml.setAddress(rda->station()->address());
rml.setEchoRequested(false);
rml.addArg(cartnum);
emit rmlEcho(&rml);
}
QString SasUsi::PrettifyCommand(const char *cmd) const
{
QString ret;
if(cmd[0]<26) {
ret=QString().sprintf("^%c%s",'@'+cmd[0],cmd+1);
}
else {
ret=cmd;
}
return ret;
}