// rdlivewire.cpp // // A LiveWire Node Driver for Rivendell // // (C) Copyright 2007-2019 Fred Gleason // // 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 #include #include #include #include #include #include #include #include AString::AString() : QString() { } AString::AString(const AString &lhs) : QString(lhs) { } AString::AString(const QString &lhs) : QString(lhs) { } QStringList AString::split(const QString &sep,const QString &esc) const { if(esc.isEmpty()) { return QStringList::split(sep,*this); } QStringList list; bool escape=false; QChar e=esc.at(0); list.push_back(QString()); for(int i=0;iconnectToHost(hostname,port); } bool RDLiveWire::loadSettings(const QString &hostname,Q_UINT16 port, const QString &passwd,unsigned base_output) { int passes=50; live_load_ver_count=1; connectToHost(hostname,port,passwd,base_output); while(--passes>0) { usleep(100000); qApp->processEvents(); if(live_load_ver_count==0) { return true; } } return false; } QString RDLiveWire::deviceName() const { return live_device_name; } QString RDLiveWire::protocolVersion() const { return live_protocol_version; } QString RDLiveWire::systemVersion() const { return live_system_version; } int RDLiveWire::sources() const { return live_sources; } int RDLiveWire::destinations() const { return live_destinations; } int RDLiveWire::channels() const { return live_channels; } int RDLiveWire::gpis() const { return live_gpis; } int RDLiveWire::gpos() const { return live_gpos; } unsigned RDLiveWire::gpiChannel(int slot,int line) const { return live_gpi_channels[slot][line]; } unsigned RDLiveWire::gpoChannel(int slot,int line) const { return live_gpo_channels[slot][line]; } bool RDLiveWire::gpiState(int slot,int line) const { return live_gpi_states[slot][line]; } bool RDLiveWire::gpoState(int slot,int line) const { return live_gpo_states[slot][line]; } void RDLiveWire::gpiSet(int slot,int line,unsigned interval) { QString cmd=QString().sprintf("GPI %d ",slot+1); for(int i=0;i0) { live_gpi_timers[slot*RD_LIVEWIRE_GPIO_BUNDLE_SIZE+line]-> start(interval,true); } emit gpiChanged(live_id,slot,line,true); } void RDLiveWire::gpiReset(int slot,int line,unsigned interval) { QString cmd=QString().sprintf("GPI %d ",slot+1); for(int i=0;i0) { live_gpi_timers[slot*RD_LIVEWIRE_GPIO_BUNDLE_SIZE+line]-> start(interval,true); } emit gpiChanged(live_id,slot,line,false); } void RDLiveWire::gpoSet(int slot,int line,unsigned interval) { QString cmd=QString().sprintf("GPO %d ",slot+1); for(int i=0;i0) { live_gpo_timers[slot*RD_LIVEWIRE_GPIO_BUNDLE_SIZE+line]-> start(interval,true); } emit gpoChanged(live_id,slot,line,true); } void RDLiveWire::gpoReset(int slot,int line,unsigned interval) { QString cmd=QString().sprintf("GPO %d ",slot+1); for(int i=0;i0) { live_gpo_timers[slot*RD_LIVEWIRE_GPIO_BUNDLE_SIZE+line]-> start(interval,true); } emit gpoChanged(live_id,slot,line,false); } void RDLiveWire::setRoute(int src_num,int dest_slot) const { QString str; str=QString().sprintf("DST %d ADDR:\"239.192.%d.%d\"\r\n", dest_slot+1,src_num/256,src_num%256); SendCommand(str); } void RDLiveWire::connectedData() { QString str="LOGIN"; if(!live_password.isEmpty()) { str+=(" "+live_password); } SendCommand(str); SendCommand("VER"); } void RDLiveWire::connectionClosedData() { if(!live_watchdog_state) { live_watchdog_state=true; int holdoff=GetHoldoff(); emit watchdogStateChanged(live_id,QString().sprintf( "connection to LiveWire node at %s:%d closed, attempting reconnect, holdoff = %d mS", (const char *)live_hostname,live_tcp_port,holdoff)); live_holdoff_timer->start(holdoff,true); } } void RDLiveWire::readyReadData() { char buf[RD_LIVEWIRE_MAX_CMD_LENGTH]; int n; while((n=live_socket->readBlock(buf,RD_LIVEWIRE_MAX_CMD_LENGTH))>0) { buf[n]=0; for(int i=0;i=RD_LIVEWIRE_MAX_CMD_LENGTH) { fprintf(stderr,"LiveWire: status string truncated"); live_ptr=0; } } } } void RDLiveWire::errorData(QAbstractSocket::SocketError err) { int interval=RDLIVEWIRE_RECONNECT_MIN_INTERVAL; switch(err) { case QAbstractSocket::ErrConnectionRefused: live_watchdog_state=true; interval=GetHoldoff(); emit watchdogStateChanged(live_id,QString().sprintf( "connection to LiveWire node at %s:%d refused, attempting reconnect, holdoff = %d mS", (const char *)live_hostname, live_tcp_port,interval)); live_holdoff_timer->start(interval,true); break; default: rda->syslog(LOG_WARNING, "socket error on connection to LiveWire node at %s:%d: %s", (const char *)live_hostname, live_tcp_port, (const char *)RDSocketStrings(err)); break; } } void RDLiveWire::gpiTimeoutData(int id) { int chan=id/RD_LIVEWIRE_GPIO_BUNDLE_SIZE; int line=id%RD_LIVEWIRE_GPIO_BUNDLE_SIZE; QString cmd=QString().sprintf("GPI %d ",chan+1); for(int i=0;istart(holdoff,true); } void RDLiveWire::holdoffData() { ResetConnection(); } void RDLiveWire::resetConnectionData() { live_socket->close(); connectToHost(live_hostname,live_tcp_port,live_password,live_base_output); } void RDLiveWire::DespatchCommand(const QString &cmd) { int offset=cmd.find(" "); QString opcode=cmd.left(offset); QString str; if(opcode=="VER") { ReadVersion(cmd.right(cmd.length()-offset-1)); } if(opcode=="SET") { } if(opcode=="SRC") { ReadSources(cmd.right(cmd.length()-offset-1)); } if(opcode=="DST") { ReadDestinations(cmd.right(cmd.length()-offset-1)); } if(opcode=="GPO") { ReadGpos(cmd.right(cmd.length()-offset-1)); } if(opcode=="GPI") { ReadGpis(cmd.right(cmd.length()-offset-1)); } if(opcode=="CFG") { str=cmd.right(cmd.length()-offset-1); offset=str.find(" "); if(str.left(offset)=="GPO") { ReadGpioConfig(str.right(str.length()-offset-1)); } } } void RDLiveWire::SendCommand(const QString &cmd) const { live_socket->writeBlock((cmd+"\r\n").ascii(),cmd.length()+2); } void RDLiveWire::ReadVersion(const QString &cmd) { QStringList f0; QStringList f1; if(!live_connected) { f0=AString(cmd).split(" ","\""); for(int i=0;i0) { SendCommand("SRC"); } } if(f1[0]=="NDST") { int delimiter=f1[1].find("/"); if(delimiter<0) { live_destinations=f1[1].toInt(); } else { live_destinations=f1[1].left(delimiter).toInt(); live_channels=f1[1].right(f1[1].length()-delimiter-1).toInt(); } if(live_destinations>0) { SendCommand("DST"); } } if(f1[0]=="NGPI") { live_gpis=f1[1].toInt(); QSignalMapper *mapper=new QSignalMapper(this); connect(mapper,SIGNAL(mapped(int)),this,SLOT(gpiTimeoutData(int))); for(int i=0;isetMapping(live_gpi_timers.back(), i*RD_LIVEWIRE_GPIO_BUNDLE_SIZE+j); connect(live_gpi_timers.back(),SIGNAL(timeout()),mapper,SLOT(map())); } } if(!live_gpi_initialized) { if(live_gpis>0) { SendCommand("ADD GPI"); } live_gpi_initialized=true; } } if(f1[0]=="NGPO") { live_gpos=f1[1].toInt(); QSignalMapper *mapper=new QSignalMapper(this); connect(mapper,SIGNAL(mapped(int)),this,SLOT(gpoTimeoutData(int))); for(int i=0;isetMapping(live_gpo_timers.back(), i*RD_LIVEWIRE_GPIO_BUNDLE_SIZE+j); connect(live_gpo_timers.back(),SIGNAL(timeout()),mapper,SLOT(map())); } } if(!live_gpo_initialized) { if(live_gpos>0) { SendCommand("CFG GPO"); SendCommand("ADD GPO"); } live_gpo_initialized=true; } } } } live_connected=true; emit connected(live_id); } if(live_load_ver_count>0) { live_load_ver_count--; } if(live_watchdog_state) { live_watchdog_state=false; emit watchdogStateChanged(live_id,QString().sprintf( "connection to LiveWire node at %s:%d restored", (const char *)live_hostname,live_tcp_port)); } live_watchdog_timer->start(RDLIVEWIRE_WATCHDOG_INTERVAL,true); live_watchdog_timeout_timer->stop(); live_watchdog_timeout_timer->start(RDLIVEWIRE_WATCHDOG_TIMEOUT,true); } void RDLiveWire::ReadSources(const QString &cmd) { QHostAddress addr; QStringList f1; RDLiveWireSource *src=new RDLiveWireSource(); QStringList f0=AString(cmd).split(" ","\""); src->setSlotNumber(f0[0].toInt()); for(int i=1;isetPrimaryName(f1[1]); } if(f1[0]=="LABL") { src->setLabelName(f1[1]); } if(f1[0]=="FASM") { // ???? } if(f1[0]=="RTPE") { src->setRtpEnabled(f1[1].toInt()); } if(f1[0]=="RTPA") { addr.setAddress(f1[1]); src->setStreamAddress(addr); } if(f1[0]=="INGN") { src->setInputGain(f1[1].toInt()); } if(f1[0]=="SHAB") { src->setShareable(f1[1].toInt()); } if(f1[0]=="NCHN") { src->setChannels(f1[1].toInt()); } if(f1[0]=="RTPP") { // ???? } } } emit sourceChanged(live_id,src); delete src; } void RDLiveWire::ReadDestinations(const QString &cmd) { QHostAddress addr; QStringList f1; RDLiveWireDestination *dst=new RDLiveWireDestination(); QStringList f0=AString(cmd).split(" ","\""); dst->setSlotNumber(f0[0].toInt()); for(int i=1;isetPrimaryName(f1[1]); } if(f1[0]=="ADDR") { addr.setAddress(f1[1]); dst->setStreamAddress(addr); } if(f1[0]=="NCHN") { dst->setChannels(f1[1].toInt()); } if(f1[0]=="LOAD") { dst->setOutputGain((RDLiveWireDestination::Load)f1[1].toInt()); } if(f1[0]=="OUGN") { dst->setOutputGain(f1[1].toInt()); } } } emit destinationChanged(live_id,dst); delete dst; } void RDLiveWire::ReadGpis(const QString &cmd) { // // FIXME: This is currently emitting the relative slot number, which is // wrong. How do we get the associated source number? // // printf("GPI: %s\n",(const char *)cmd); int offset=cmd.find(" "); int slot=cmd.left(offset).toInt()-1; QString str=cmd.right(cmd.length()-offset-1).lower(); for(int i=0;i=0) { ret=str.left(offset); } return ret; } void RDLiveWire::ResetConnection() { live_socket->close(); connectToHost(live_hostname,live_tcp_port,live_password,live_base_output); } int RDLiveWire::GetHoldoff() { return (int)(RDLIVEWIRE_RECONNECT_MIN_INTERVAL+ (RDLIVEWIRE_RECONNECT_MAX_INTERVAL- RDLIVEWIRE_RECONNECT_MIN_INTERVAL)* (double)random()/(double)RAND_MAX); }