// livewire_mcastgpio.cpp // // A Rivendell multicast GPIO driver for LiveWire networks. // // (C) Copyright 2013-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 #include #include "globals.h" #include "livewire_mcastgpio.h" LiveWireMcastGpio::LiveWireMcastGpio(RDMatrix *matrix,QObject *parent) : Switcher(matrix,parent) { livewire_gpio_notify=NULL; long sockopt; struct sockaddr_in sa; QString sql; RDSqlQuery *q; // // Get Matrix Parameters // livewire_stationname=rda->station()->name(); livewire_matrix=matrix->matrix(); livewire_gpios=matrix->gpis(); livewire_interface_addr=matrix->ipAddress(RDMatrix::Primary); // // Initialize Serial Numbers // time_t t; t=time(&t); srand(t); livewire_gpio_send_serial=rand(); // // Timers // livewire_gpi_timer_mapper=new QSignalMapper(this); connect(livewire_gpi_timer_mapper,SIGNAL(mapped(int)), this,SLOT(gpiTimeoutData(int))); for(unsigned i=0;isetMapping(livewire_gpi_timers.back(),i); connect(livewire_gpi_timers.back(),SIGNAL(timeout()), livewire_gpi_timer_mapper,SLOT(map())); } livewire_gpo_out_timer_mapper=new QSignalMapper(this); connect(livewire_gpo_out_timer_mapper,SIGNAL(mapped(int)), this,SLOT(gpoOutTimeoutData(int))); for(unsigned i=0;isetMapping(livewire_gpo_out_timers.back(),i); connect(livewire_gpo_out_timers.back(),SIGNAL(timeout()), livewire_gpo_out_timer_mapper,SLOT(map())); livewire_gpo_out_states.push_back(false); } livewire_gpo_in_timer_mapper=new QSignalMapper(this); connect(livewire_gpo_in_timer_mapper,SIGNAL(mapped(int)), this,SLOT(gpoInTimeoutData(int))); for(unsigned i=0;isetMapping(livewire_gpo_in_timers.back(),i); connect(livewire_gpo_in_timers.back(),SIGNAL(timeout()), livewire_gpo_in_timer_mapper,SLOT(map())); livewire_gpo_in_states.push_back(false); } // // GPIO Write Socket // if((livewire_gpio_write_socket=socket(PF_INET,SOCK_DGRAM,0))<0) { rda->syslog(LOG_ERR,"unable to create GPIO write socket [%s]", strerror(errno)); return; } sockopt=O_NONBLOCK; fcntl(livewire_gpio_write_socket,F_SETFL,sockopt); memset(&sa,0,sizeof(sa)); sa.sin_family=AF_INET; sa.sin_port=htons(htons(RD_LIVEWIRE_GPIO_SEND_PORT)); sa.sin_addr.s_addr= htonl(matrix->ipAddress(RDMatrix::Primary).ip4Addr()); if(bind(livewire_gpio_write_socket,(struct sockaddr *)&sa,sizeof(sa))<0) { rda->syslog(LOG_ERR,"unable to bind GPIO write socket [%s]", strerror(errno)); return; } // // GPIO Read Socket // if((livewire_gpio_read_socket=socket(PF_INET,SOCK_DGRAM,0))<0) { rda->syslog(LOG_ERR,"unable to create GPIO read socket [%s]", strerror(errno)); return; } sockopt=O_NONBLOCK; fcntl(livewire_gpio_read_socket,F_SETFL,sockopt); memset(&sa,0,sizeof(sa)); sa.sin_family=AF_INET; sa.sin_port=htons(RD_LIVEWIRE_GPIO_RECV_PORT); sa.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(livewire_gpio_read_socket,(struct sockaddr *)&sa,sizeof(sa))<0) { rda->syslog(LOG_ERR,"unable to bind GPIO socket [%s]",strerror(errno)); return; } livewire_gpio_notify= new QSocketNotifier(livewire_gpio_read_socket,QSocketNotifier::Read,this); connect(livewire_gpio_notify,SIGNAL(activated(int)), this,SLOT(gpioActivatedData(int))); subscribe(QHostAddress(RD_LIVEWIRE_GPIO_MCAST_ADDR)); // // Source Table // sql=QString("select ")+ "SLOT,"+ // 00 "SOURCE_NUMBER,"+ // 01 "IP_ADDRESS "+ // 02 "from LIVEWIRE_GPIO_SLOTS "+ "where (STATION_NAME=\""+RDEscapeString(livewire_stationname)+"\")&&"+ QString().sprintf("(MATRIX=%d) ",livewire_matrix)+ "order by SLOT"; q=new RDSqlQuery(sql); while(q->next()) { if(q->value(0).toInt()<((int)livewire_gpios/RD_LIVEWIRE_GPIO_BUNDLE_SIZE)) { livewire_source_numbers[q->value(0).toInt()]=q->value(1).toInt(); livewire_surface_addresses[q->value(0).toInt()]= QHostAddress(q->value(2).toString()); } } delete q; } LiveWireMcastGpio::~LiveWireMcastGpio() { if(livewire_gpio_notify!=NULL) { close(livewire_gpio_read_socket); delete livewire_gpio_notify; close(livewire_gpio_write_socket); } for(unsigned i=0;icommand()) { case RDMacro::GO: if((cmd->argQuantity()!=5)|| (cmd->arg(2).toInt()<1)|| (cmd->arg(2).toInt()>(int)livewire_gpios)|| ((cmd->arg(1).lower()!="i")&& (cmd->arg(1).lower()!="o"))) { cmd->acknowledge(false); emit rmlEcho(cmd); return; } if(cmd->arg(1).lower()=="i") { slot=(cmd->arg(2).toInt()-1)/5; line=(cmd->arg(2).toInt()-1)%5; if(livewire_source_numbers[slot]<=0) { cmd->acknowledge(false); emit rmlEcho(cmd); return; } if(cmd->arg(4).toInt()>0) { livewire_gpo_in_timers[cmd->arg(2).toInt()-1]-> start(cmd->arg(4).toInt(),true); } ProcessGpoIn(livewire_source_numbers[slot],line,cmd->arg(3).toInt()); livewire_gpo_in_states[cmd->arg(2).toInt()-1]=cmd->arg(3).toInt(); } if(cmd->arg(1).lower()=="o") { slot=(cmd->arg(2).toInt()-1)/5; line=(cmd->arg(2).toInt()-1)%5; if(livewire_source_numbers[slot]<=0) { cmd->acknowledge(false); emit rmlEcho(cmd); return; } if(cmd->arg(4).toInt()>0) { livewire_gpo_out_timers[cmd->arg(2).toInt()-1]-> start(cmd->arg(4).toInt(),true); } ProcessGpoOut(livewire_source_numbers[slot],line,cmd->arg(3).toInt()); livewire_gpo_out_states[cmd->arg(2).toInt()-1]=cmd->arg(3).toInt(); } cmd->acknowledge(true); emit rmlEcho(cmd); break; default: cmd->acknowledge(false); emit rmlEcho(cmd); break; } } void LiveWireMcastGpio::gpioActivatedData(int sock) { int n; char data[1500]; struct sockaddr_in sa; socklen_t sa_len=sizeof(sa); uint32_t serial; while((n=recvfrom(livewire_gpio_read_socket,data,1500,MSG_DONTWAIT, (struct sockaddr *)(&sa),&sa_len))>0) { serial=((0xFF&data[4])<<24)+((0xFF&data[5])<<16)+((0xFF&data[6])<<8)+ (0xFF&data[7]); if((livewire_gpio_recv_serials[sa.sin_addr.s_addr]!=serial)&& (livewire_gpio_recv_serials[sa.sin_addr.s_addr]!=(serial-1))) { livewire_gpio_recv_serials[sa.sin_addr.s_addr]=serial; ProcessGpi(QHostAddress(ntohl(sa.sin_addr.s_addr)), ((0xFF&data[23])<<8)+(0xFF&data[24]),0x08-(0xff&data[25]), (data[27]&0x40)!=0,(data[27]&0x0A)!=0); } } } void LiveWireMcastGpio::gpiTimeoutData(int gpi) { emit gpiChanged(livewire_matrix,gpi,false); } void LiveWireMcastGpio::gpoInTimeoutData(int gpo) { int slot=gpo/5; int line=gpo%5; if(livewire_source_numbers[slot]>0) { ProcessGpoIn(livewire_source_numbers[slot],line,!livewire_gpo_in_states[gpo]); livewire_gpo_in_states[gpo]=!livewire_gpo_in_states[gpo]; } } void LiveWireMcastGpio::gpoOutTimeoutData(int gpo) { int slot=gpo/5; int line=gpo%5; if(livewire_source_numbers[slot]>0) { ProcessGpoOut(livewire_source_numbers[slot],line,!livewire_gpo_out_states[gpo]); livewire_gpo_out_states[gpo]=!livewire_gpo_out_states[gpo]; } } void LiveWireMcastGpio::ProcessGpi(const QHostAddress &src_addr,int chan, unsigned line,bool state,bool pulse) { for(std::map::const_iterator it=livewire_source_numbers.begin(); it!=livewire_source_numbers.end();it++) { if((it->second==chan)&& ((livewire_surface_addresses[it->first].isNull())|| (livewire_surface_addresses[it->first]==src_addr))) { emit gpiChanged(livewire_matrix,5*it->first+line,state); if(pulse) { livewire_gpi_timers[5*it->first+line]-> start(RD_LIVEWIRE_GPIO_PULSE_WIDTH,true); } } } } void LiveWireMcastGpio::ProcessGpoIn(int chan,unsigned line,bool state) { // // Destination Address // struct sockaddr_in sa; memset(&sa,0,sizeof(sa)); sa.sin_family=AF_INET; sa.sin_port=htons(RD_LIVEWIRE_GPIO_RECV_PORT); sa.sin_addr.s_addr= htonl(QHostAddress(RD_LIVEWIRE_GPIO_MCAST_ADDR).toIPv4Address()); uint8_t data[60]={0x03,0x00,0x02,0x07,0x36,0x0B,0x97,0xA9, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 'W','R','N','I',0x00,0x04,0x00,0x03,0xF6, 0x05,0x07,0xCA,0xFF,0xFF,0xFF,0xFD,0x07, 0x01,0xFF,0xFF,0xFF,0xFC,0x09,0x00,0x02, 0x15,0x07,0x00,0x12,0x00,0x8F,0xFF,0xFF, 0xFF,0xFF,0x09,0x00,0x02,0x15,0x07,0x00, 0x00,0x00,0x8F}; data[4]=0xFF&(livewire_gpio_send_serial>>24); data[5]=0xFF&(livewire_gpio_send_serial>>16); data[6]=0xFF&(livewire_gpio_send_serial>>8); data[7]=0xFF&livewire_gpio_send_serial; data[23]=0xFF&(chan>>8); data[24]=0xFF&chan; data[25]=0x08-(0x07&line); if(state) { data[27]|=0x40; } else { data[27]&=~0x40; } data[27]&=~0x0A; // No pulse sendto(livewire_gpio_write_socket,data,28,MSG_DONTWAIT, (struct sockaddr *)(&sa),sizeof(sa)); livewire_gpio_send_serial++; data[4]=0xFF&(livewire_gpio_send_serial>>24); data[5]=0xFF&(livewire_gpio_send_serial>>16); data[6]=0xFF&(livewire_gpio_send_serial>>8); data[7]=0xFF&livewire_gpio_send_serial; sendto(livewire_gpio_write_socket,data,60,MSG_DONTWAIT, (struct sockaddr *)(&sa),sizeof(sa)); livewire_gpio_send_serial+=2; } void LiveWireMcastGpio::ProcessGpoOut(int chan,unsigned line,bool state) { // // Destination Address // struct sockaddr_in sa; memset(&sa,0,sizeof(sa)); sa.sin_family=AF_INET; sa.sin_port=htons(RD_LIVEWIRE_GPIO_SEND_PORT); sa.sin_addr.s_addr= htonl(QHostAddress(RD_LIVEWIRE_GPIO_MCAST_ADDR).toIPv4Address()); /* * FIXME: Sending this to the mcast addr causes ALL instances * of the source to switch. How does one specify a particular * surface? Sending to the surface addr does not work! * for(std::map::const_iterator it=livewire_source_numbers.begin(); it!=livewire_source_numbers.end();it++) { if((it->second==chan)&&(!livewire_surface_addresses[it->first].isNull())) { sa.sin_addr.s_addr= htonl(livewire_surface_addresses[it->first].toIPv4Address()); } } rda->syslog(LOG_DEBUG,"using %s", (const char *)QHostAddress(ntohl(sa.sin_addr.s_addr)).toString()); */ uint8_t data[28]={0x03,0x00,0x02,0x07,0xC6,0x04,0x55,0x1E, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 'I','N','D','I',0x00,0x01,0x00,0x00, 0x00,0x00,0x07,0x00}; data[4]=0xFF&(livewire_gpio_send_serial>>24); data[5]=0xFF&(livewire_gpio_send_serial>>16); data[6]=0xFF&(livewire_gpio_send_serial>>8); data[7]=0xFF&livewire_gpio_send_serial; data[23]=0xFF&(chan>>8); data[24]=0xFF&chan; data[25]=0xFF&(0x0D-line); data[27]=0xFF&state; sendto(livewire_gpio_write_socket,data,28,MSG_DONTWAIT, (struct sockaddr *)(&sa),sizeof(sa)); livewire_gpio_send_serial++; data[4]=0xFF&(livewire_gpio_send_serial>>24); data[5]=0xFF&(livewire_gpio_send_serial>>16); data[6]=0xFF&(livewire_gpio_send_serial>>8); data[7]=0xFF&livewire_gpio_send_serial; sendto(livewire_gpio_write_socket,data,28,MSG_DONTWAIT, (struct sockaddr *)(&sa),sizeof(sa)); livewire_gpio_send_serial+=2; } QString LiveWireMcastGpio::AddressString(uint32_t addr) const { return QString().sprintf("%d.%d.%d.%d", 0xFF&addr, 0xFF&(addr>>8), 0xFF&(addr>>16), 0xFF&(addr>>24)); } void LiveWireMcastGpio::subscribe(const QHostAddress &addr) const { subscribe(htonl(addr.toIPv4Address())); } void LiveWireMcastGpio::subscribe(const uint32_t addr) const { struct ip_mreqn mreq; memset(&mreq,0,sizeof(mreq)); mreq.imr_multiaddr.s_addr=addr; mreq.imr_address.s_addr=htonl(livewire_interface_addr.toIPv4Address()); mreq.imr_ifindex=0; if(setsockopt(livewire_gpio_read_socket,SOL_IP,IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq))!=0) { if(errno!=EADDRINUSE) { rda->syslog(LOG_WARNING,"unable to subscribe to %s on %s [%s]", (const char *)AddressString(addr), (const char *)livewire_interface_addr.toString(), strerror(errno)); } } } void LiveWireMcastGpio::unsubscribe(const QHostAddress &addr) const { unsubscribe(htonl(addr.toIPv4Address())); } void LiveWireMcastGpio::unsubscribe(const uint32_t addr) const { struct ip_mreqn mreq; memset(&mreq,0,sizeof(mreq)); mreq.imr_multiaddr.s_addr=htonl(addr); mreq.imr_address.s_addr=htonl(livewire_interface_addr.toIPv4Address()); mreq.imr_ifindex=0; if(setsockopt(livewire_gpio_read_socket,SOL_IP,IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq))!=0) { if(errno!=ENODEV) { rda->syslog(LOG_WARNING,"unable to unsubscribe from %s on %s [%s]", (const char *)AddressString(addr), (const char *)livewire_interface_addr.toString(), strerror(errno)); } } }