diff --git a/boss/rdtib.f b/boss/rdtib.f index 37decfc87..e8dc312d7 100644 --- a/boss/rdtib.f +++ b/boss/rdtib.f @@ -38,11 +38,11 @@ subroutine rdtib(idcb,ip) C dimension ip(5) C for RMPAR - integer*2 ibuf(40) + integer*2 ibuf(100) C buffer to hold input records integer fmpread,ichcm_ch C ibadrd - max length of line in ibad.ctl to read - data ibadrd /80/ + data ibadrd /200/ C ICLASS - class to send to IBCON C NREC - number of records in class C @@ -55,6 +55,8 @@ subroutine rdtib(idcb,ip) C LAST MODIFIED: 800224 C WHO WHEN DESCRIPTION C GAG 901220 Restructured loop and added call to logit. +C Lerner 120723 Increased buffer length to support network devices +C Lerner 210427 Added new 2020 header c C# LAST COMPC'ED 870115:04:18 # C @@ -64,7 +66,7 @@ subroutine rdtib(idcb,ip) C call fmpopen(idcb,FS_ROOT//'/control/ibad.ctl',ierr,'r',id) if (ierr.lt.0) return - call ifill_ch(ibuf,1,80,' ') + call ifill_ch(ibuf,1,200,' ') ilen = fmpread(idcb,ierr,ibuf,ibadrd) call lower(ibuf,ilen) iclass = 0 @@ -79,8 +81,8 @@ subroutine rdtib(idcb,ip) nrec = nrec + 1 call put_buf(iclass,ibuf,-iflch(ibuf,ilen),' ',' ') C Put record into class record - call ifill_ch(ibuf,1,80,' ') endif + call ifill_ch(ibuf,1,200,' ') ilen = fmpread(idcb,ierr,ibuf,ibadrd) call lower(ibuf,ilen) end do diff --git a/control/fserr.ctl b/control/fserr.ctl index 5f314cc31..749fdb7a5 100644 --- a/control/fserr.ctl +++ b/control/fserr.ctl @@ -3269,6 +3269,18 @@ GPIB/232 (EOFL) serial buffer overflow detected by converter, ?W IB -544 GPIB/232 (EFRM) serial data framing error detected by converter, ?W "" +IB -601 +Failed to open communication with network device, ?W +"" +IB -602 +Failed to send command to network device +"" +IB -603 +Failed to read from network device +"" +IB -604 +Blank command provided to network device +"" IF -301 Error can't open ifatt.ctl. "" diff --git a/ibcon/Makefile b/ibcon/Makefile index fb907447e..2c24bdbbe 100644 --- a/ibcon/Makefile +++ b/ibcon/Makefile @@ -1,5 +1,8 @@ +# Lerner 2021-04-27 Adapted new 64-bit version for OSO code + OBJECTS = ibcon.o opbrd.o opdev.o rddev.o wrdev.o sib.o statbrd.o rspdev.o\ -echo_out.o find_delay.o iclrdev.o +echo_out.o find_delay.o iclrdev.o check_network.o handle_network.o\ +util_network.o ifeq (/usr/local/include/sys/ugpib.h,$(wildcard /usr/local/include/sys/ugpib.h)) CPPFLAGS += -DCONFIG_GPIB -DNI_DRIVER diff --git a/ibcon/check_network.c b/ibcon/check_network.c new file mode 100644 index 000000000..0c4612668 --- /dev/null +++ b/ibcon/check_network.c @@ -0,0 +1,319 @@ +/***************************************************************************** + * + * FILE: check_network.c + * + * Check the provided line coming from 'ibad.tcl' to see if it contains + * a network-controlled device --- if so, process it here. + * + * Network-controlled devices are GPIB-devices which are controlled either + * using TCP/IP communication with a Prologix GPIB-ethernet controller box or + * directly via a TCP/IP interface instead of via a dedicated GPIB-card in + * the FS computer. + * + * Note that this procedure is very general and also allows you to talk + * with any device that are connected directly to a TCP/IP-network and uses + * an ASCII-syntax. + * + * This version of IBCON allows you to mix and match between a GPIB-card, + * a Prologix-box controlling several devices on a common GPIB-bus, + * multiple GPIB-boxes each controlling a single GPIB-device or + * any combination of these options. + * + * This procedure is part of the 'ibcon' program. + * + * Network-controlled devices use the following syntax in the 'ibad.ctl' + * file: + * + * MN=net,IP-address,port,/command1[/command2[/command3...]] + * + * where 'MN' is a 2-character mnemonic used to identify the device action, + * 'IP-address' is the IP-address of the device or the Prologix-box given + * in 'a.b.c.d' notation, 'port' is the port number to use (port should be + * "1234" for Prologix-boxes but can be other things for other network-based + * devices --- some network-based GPIB-devices seem to use port "5025", + * for example) and the command sequence is the set of commands to send when + * communicating with the device, which typically will include + * Prologix-commands used to set up the Prologix box as well as + * the GPIB-command(s) to the device itself. The command sequence should + * start with the character used to separate the different commands from each + * other (even if there is only one command). Typicaly "/" is used, but you + * are free to choose something else. + * + * Two special commands are recognized: '##' means that the command + * passed to IBCON at the time of execution should be inserted at that point + * in the sequence, while '$$' means that the device has generated a reply + * that should be read at this point in the sequence. + * + * A '&' can be appended to any command; this command will then be followed + * automatically by the ':SYST:ERR?' command and any error reported will + * be written to the log. For example, the command sequence + * '*RST/:INIT:CONT ON&' will consecutively send the following three commands: + * '*RST', ':INIT:CONT ON' and ':SYST:ERR?', then wait for the reply and + * report an error if the reply is not '+0,"No error"' - the actual test is + * for the "0". + * + * Different commands can be sent to the same device by giving them different + * mnemonics, and for network-controlled devices the mnemonics should be seen + * more as symbols for different actions instead of different devices. + * + * At Onsala Space Observatory, we use two HP-devices for CABLE and CLOCK + * measurements, both being controlled by individual Prologix-boxes. + * The 'ibad.ctl' file looks like this: + * + * CA=net,192.16.6.15,1234,/++auto 0/++addr 2/++read_tmo_ms 1000/++read 10/$$ + * CB=net,192.16.6.16,1234,/++addr 3/++auto 1/:READ?/$$/++auto 0/:INIT:CONT ON + * + * We also have another HP-device that can be used for CLOCK measurements. + * It uses a similar syntax but it is connected directly to + * the TCP/IP-network. We can control that one with the following + * 'ibad.ctl' line: + * + * CX=net,192.16.6.17,5025,/:READ?/$$/:INIT:CONT ON + * + * The following example shows four mnemonics to talk to the same device: + * the first one is a standard read request, the second one is a reset + * command and the last two ones are for general commands and questions, + * respectively. + * + * CB=net,192.16.6.16,1234,/++addr 3/++auto 1/:READ?/$$/++auto 0/:INIT:CONT ON + * CC=net,192.16.6.16,1234,/++addr 3/++auto 0/*RST + * CD=net,192.16.6.16,1234,/++addr 3/++auto 0/## + * CE=net,192.16.6.16,1234,/++addr 3/++auto 1/##/$$ + * + * HISTORY + * + * who when what + * --------- ----------- ---------------------------------------------- + * lerner 26 Jul 2012 Original version + * lerner 12 Feb 2013 Added user-specified port-number to enable + * communication with network-based devices + * lerner 7 May 2021 Adapted for inclusion in FS10 and added dynamic + * command separator + * + *****************************************************************************/ + +#include +#include +#include +#include + + + +/***************************************************************************** + * + * Macros + * + *****************************************************************************/ + +#define MAX_ACTIONS 50 +#define MAX_SEQUENCES 20 + + + +/***************************************************************************** + * + * Global variables + * + *****************************************************************************/ + +/* Network device variables */ + +int network_devices = 0; +char network_address[MAX_ACTIONS][16]; +char network_port[MAX_ACTIONS][8]; +int network_socket[MAX_ACTIONS]; + +/* Network action variables */ + +int network_actions = 0; +char network_mnemonic[MAX_ACTIONS][3]; +char *network_sequence[MAX_ACTIONS][MAX_SEQUENCES]; +int network_check[MAX_ACTIONS][MAX_SEQUENCES]; +int network_device[MAX_ACTIONS]; + + + +/***************************************************************************** + * + * Subroutine declarations + * + *****************************************************************************/ + +int open_network(int box); +int close_network(int box); +int send_network(int device, char *buf, unsigned int timeout); +int read_network(int device, char *buf, size_t buf_len, unsigned int timeout); +int network_connected(int device); + +int check_network__(char *line, int *length); + +int logit(); + + + +/***************************************************************************** + * + * check_network + * + * check the line from the ibad.ctl file --- return immediately with '0' + * if it is not a network-controlled device, otherwise process the line + * and return the assigned index in the network device tables or '-1' + * in case of an error + * + *****************************************************************************/ + +int check_network__(char *line, int *length) { + + char setup[512], string[512], address[128], port[128], commands[512]; + char separator[2], *p, *p1; + int action, device; + int i; + + /* Copy over the line to our work string */ + + if ( *length >= sizeof(setup) ) { + logit("Too large lines in file ibad.ctl", 0, NULL); + return(-1); + } + + strncpy(setup, line, *length); + setup[*length] = '\0'; + + logit(setup, 0, NULL); + + /* Return immediately, if this is not a network device */ + + if ( strncmp(&setup[3], "net", 3) != 0 ) + return(0); + + /* Verify that we don't have too many network device actions */ + + if ( ++network_actions >= MAX_ACTIONS ) { + logit("Too many network device actions declared in file ibad.ctl", 0, NULL); + snprintf(string, sizeof(string), "Offending line: '%s'", setup); + logit(string, 0, NULL); + return(-1); + } + + /* Store the mnemonic of the new network device action action --- note that + we don't use element '0' since we want positive numbers */ + + action = network_actions; + + strncpy(network_mnemonic[action], setup, 2); + network_mnemonic[action][2] = '\0'; + + /* Get the IP-address of the network device --- let's use strtok to split + the string */ + + strtok(setup, ","); + + if ( ( p = strtok(NULL, ",") ) == NULL ) { + logit("No comma after word 'net' in file ibad.ctl", 0, NULL); + snprintf(string, sizeof(string), "Offending line: '%s'", setup); + logit(string, 0, NULL); + return(-1); + } + + strncpy(address, p, sizeof(address)); + + /* Now get the port number from the string */ + + if ( ( p = strtok(NULL, ",") ) == NULL ) { + logit("No comma after IP-address in file ibad.ctl", 0, NULL); + snprintf(string, sizeof(string), "Offending line: '%s'", setup); + logit(string, 0, NULL); + return(-1); + } + + strncpy(port, p, sizeof(port)); + + /* Now get the command sequence from the string --- note that we change + the delimiter since commands may contain commas */ + + if ( ( p = strtok(NULL, "\n") ) == NULL ) { + logit("No comma after port-number in file ibad.ctl", 0, NULL); + snprintf(string, sizeof(string), "Offending line: '%s'", setup); + logit(string, 0, NULL); + return(-1); + } + + strncpy(commands, p, sizeof(commands)); + + /* Check if we already have registered this network device or if it is + a new one */ + + device = -1; + + for ( i = 0 ; i < network_devices ; i++ ) + if ( strcmp(network_address[i], address) == 0 ) + device = i; + + if ( device == -1 ) { + if ( strlen(address) >= sizeof(network_address[0]) ) { + logit("Bad IP-address in file ibad.ctl", 0, NULL); + snprintf(string, sizeof(string), "Offending line: '%s'", setup); + logit(string, 0, NULL); + return(-1); + } + if ( strlen(port) >= sizeof(network_port[0]) ) { + logit("Bad port-number in file ibad.ctl", 0, NULL); + snprintf(string, sizeof(string), "Offending line: '%s'", setup); + logit(string, 0, NULL); + return(-1); + } + device = network_devices++; + strcpy(network_address[device], address); + strcpy(network_port[device], port); + network_socket[device] = -1; + /* + if ( open_network(device) < 0 ) { + snprintf(string, sizeof(string), "Failed to open socket to network " + "device at '%s' port '%s' for action '%s'", + network_address[device], network_port[device], + network_mnemonic[action]); + logit(string, 0, NULL); + logit("ERROR", errno, "un"); + } + */ + } + + network_device[action] = device; + + /* Split up and store the network device action command sequence */ + + p1 = commands; + i = 0; + + separator[0] = *p1++; + separator[1] = '\0'; + + while ( ( p = strtok(p1, separator) ) != NULL ) { + if ( i + 1 >= MAX_SEQUENCES ) { + logit("Too many commands for network device in file ibad.ctl", 0, NULL); + snprintf(string, sizeof(string), "Offending line: '%s'", setup); + logit(string, 0, NULL); + return(-1); + } + network_sequence[action][i] = (char *) malloc(strlen(p) + 1); + strcpy(network_sequence[action][i], p); + network_check[action][i] = 0; + if ( network_sequence[action][i][strlen(p)-1] == '&' ) { + network_sequence[action][i][strlen(p)-1] = '\0'; + network_check[action][i] = 1; + } + i++; + p1 = NULL; + } + + network_sequence[action][i] = NULL; + + /* Write a message to the log */ + + sprintf(string, "initiated action %s on network device %s port %s " + "(%d commands)", network_mnemonic[action], network_address[device], + network_port[device], i); + logit(string, 0, NULL); + + return(action); +} diff --git a/ibcon/handle_network.c b/ibcon/handle_network.c new file mode 100644 index 000000000..d198ecfe4 --- /dev/null +++ b/ibcon/handle_network.c @@ -0,0 +1,231 @@ +/***************************************************************************** + * + * FILE: handle_network.c + * + * Handle communication with a network-controlled device. + * + * This procedure is part of the 'ibcon' program. + * + * The use of network-controlled devices is described in + * the 'check_network.c' routine. + * + * HISTORY + * + * who when what + * --------- ----------- ---------------------------------------------- + * lerner 26 Jul 2012 Original version + * lerner 7 Feb 2013 Added user-specified port-number to enable + * communication with network-based devices + * lerner 27 Apr 2021 Adapted for inclusion in FS10 + * + *****************************************************************************/ + +#include +#include +#include +#include + + + +/***************************************************************************** + * + * Macros + * + *****************************************************************************/ + +#define MAX_ACTIONS 50 +#define MAX_SEQUENCES 20 + +#define SEND_TIMEOUT 2 +#define READ_TIMEOUT 3 +#define MAX_READ_LENGTH 80 + + + +/***************************************************************************** + * + * Global variables + * + *****************************************************************************/ + +/* Network device variables --- set up by 'check_network.c' */ + +extern char network_address[MAX_ACTIONS][16]; +extern char network_port[MAX_ACTIONS][8]; +extern int network_socket[MAX_ACTIONS]; + +/* Network action variables --- set up by 'check_network.c' */ + +extern char network_mnemonic[MAX_ACTIONS][3]; +extern char *network_sequence[MAX_ACTIONS][MAX_SEQUENCES]; +extern int network_check[MAX_ACTIONS][MAX_SEQUENCES]; +extern int network_device[MAX_ACTIONS]; + + + +/***************************************************************************** + * + * Subroutine declarations + * + *****************************************************************************/ + +int open_network(int box); +int close_network(int box); +int send_network(int device, char *buf, unsigned int timeout); +int read_network(int device, char *buf, size_t buf_len, unsigned int timeout); +int network_connected(int device); + +int handle_network__(char *line, int *length, int *device, int *ierr, + long *ipcode); + +int logit(); + + + +/***************************************************************************** + * + * handle_network + * + * handle the communication with a network-controlled device using + * the sequence of commands specified in 'ibad.ctl' + * + *****************************************************************************/ + +int handle_network__(char *line, int *length, int *device, int *ierr, + long *ipcode) { + + char command[256], dynamic_command[256], string[512], error[256]; + int answer = 0, failed = 0; + int i, j; + + strncpy(dynamic_command, &line[4], *length-4); + dynamic_command[*length-4] = '\0'; + + /* Loop once or twice if we have a communication problem */ + + for ( i = 0 ; i == 0 || i == 1 && failed ; i++ ) { + + /* Open the connection with the network device, if we are not + connected --- bomb out if it fails */ + + if ( ! network_connected(*device) ) { + //logit("Trying to open network connection", 0, "un"); + if ( open_network(network_device[*device]) < 0 ) { + logit("ERROR", errno, "un"); + *ierr = -601; + memcpy((char *) ipcode, "PO", 2); + return(-1); + } + } + + /* Send the sequence of commands --- if we encounter a problem, then stop + and repeat the outer loop once --- if this is already the second + loop and we still have problems, then bomb out */ + + for ( j = 0 ; network_sequence[*device][j] != NULL ; j++ ) { + strcpy(command, network_sequence[*device][j]); + + /* Read a reply from the network device, if we are expecting a reply */ + + if ( strcmp(command, "$$") == 0 ) { + if ( read_network(*device, line, MAX_READ_LENGTH, + READ_TIMEOUT) < 0 ) { + if ( failed ) { + logit("ERROR failed to read from network device", 0, "un"); + *ierr = -603; + memcpy((char *) ipcode, "PR", 2); + close_network(network_device[*device]); + return(-1); + } + logit("Will try to reopen network communication", 0, "un"); + failed = 1; + break; + } else { + *length = strlen(line); + answer = 1; + } + } else { + + /* Insert the command we have been called with into the command + sequence, if we have been told to do that */ + + if ( strcmp(command, "##") == 0 ) { + if ( strlen(dynamic_command) == 0 ) { + logit("ERROR no command provided to network device", 0, "un"); + *ierr = -604; + memcpy((char *) ipcode, "PN", 2); + close_network(network_device[*device]); + return(-1); + } + strcpy(command, dynamic_command); + } + + /* Send the command to the network device */ + + if ( send_network(*device, command, SEND_TIMEOUT) < 0 ) { + if ( failed ) { + logit("ERROR failed to send command to network device", 0, "un"); + *ierr = -602; + memcpy((char *) ipcode, "PW", 2); + close_network(network_device[*device]); + return(-1); + } + logit("Will try to reopen network communication", 0, "un"); + failed = 1; + break; + } + } + + /* Send the error checking command, if we are supposed to do that */ + + if ( network_check[*device][j] ) { + if ( send_network(*device, ":SYST:ERR?", SEND_TIMEOUT) < 0 ) { + snprintf(string, sizeof(string), "WARNING failed when sending an " + "error checking command after command '%s'!", command); + logit(string, 0, NULL); + if ( failed ) { + logit("ERROR failed to send command to network device", 0, "un"); + *ierr = -602; + memcpy((char *) ipcode, "PW", 2); + close_network(network_device[*device]); + return(-1); + } + logit("Will try to reopen network communication", 0, "un"); + failed = 1; + break; + } else { + if ( read_network(*device, error, sizeof(error) - 1, + READ_TIMEOUT) < 0 ) { + snprintf(string, sizeof(string), "WARNING failed reading out the " + "result from the error checking command sent after " + "command '%s'!", command); + logit(string, 0, NULL); + if ( failed ) { + logit("ERROR failed to read from network device", 0, "un"); + *ierr = -603; + memcpy((char *) ipcode, "PR", 2); + close_network(network_device[*device]); + return(-1); + } + logit("Will try to reopen network communication", 0, "un"); + failed = 1; + break; + } else if ( strncmp(error, "+0", 2) != 0 && + strncmp(error, "0", 1) != 0 ) { + snprintf(string, sizeof(string), "WARNING received error message " + "'%s' after sending command '%s'!", error, command); + logit(string, 0, NULL); + } + } + } + + } + + } + + logit("Closing network connection after successful communication", 0, "un"); + + close_network(network_device[*device]); + + return(answer); +} diff --git a/ibcon/ibcon.f b/ibcon/ibcon.f index 63b1df467..f46501933 100644 --- a/ibcon/ibcon.f +++ b/ibcon/ibcon.f @@ -26,6 +26,8 @@ program ibcon C initializing MODTBL C JFHQ 940124 Re-fixed Read/Write buffer truncation bugs C DMV 941213 removed nchr2, changed logic of if statements, +C Lerner 2012-07-26 Included support for network devices +C Lerner 2021-04-27 Added new 2020 header and adapted for 64-bit C C PROGRAM STRUCTURE C 1.1. IBCON controls the I/O to the GP Interface Bus @@ -94,29 +96,33 @@ program ibcon integer*2 ibuf(ibufln),ibuf2(ibufln),istatk4(2),irdk4,ilvk4,ilck4 C - buffers for reading, writing C ILEN - length of above buffers - logical kini, kfirst, kgpib + logical kini, kfirst, kgpib, no_board C - TRUE once we have initialized C - TRUE on the first time through, C - TRUE until I know the gpib driver isn't installed +C - TRUE until we know whether the GPIB-board is needed C NDEV - # devices in module table parameter (idevln=32) C length of device file names, up to 64 characters - parameter (imaxdev=16) -C maximum number of devices on IEEE board +C parameter (imaxdev=16) + parameter (imaxdev=64) +C maximum number of devices on IEEE board - increased for network devices integer iscn_ch, ichmv, icomma, iend, iflch integer idlen,it(6) integer rddev, opbrd, iserial,opdev, wrdev, idum,statbrd,rspdev + integer check_network, handle_network integer fc_rte_prior, no_after, no_online, no_write_ren integer no_w_ren_glbl integer set_remote_enable,no_interface_clear_board integer interface_clear_converter,interface_clear_after_read + integer device, status double precision timnow,timlst(imaxdev) integer*4 oldcmd(imaxdev) integer*2 moddev(imaxdev,idevln) C - module device name integer idevid(imaxdev) C - device ids when opened - integer*2 modtbl(3,imaxdev) + integer*2 modtbl(4,imaxdev) C - module table, word 1 = mnemonic, C word 2 = 0 for talk/listen devices C 1 for talk-only devices @@ -124,6 +130,7 @@ program ibcon C +4 if SRQ supported C +8 if no_write_ren for this device C word 3 time-out value +C word 4 index in network table or '0' to indicate GPIB-board integer tmotbl(16) C table of time-out values microseconds C =0 disabled @@ -136,6 +143,7 @@ program ibcon C 5. INITIALIZED VARIABLES C data kini/.false./,kfirst/.true./,kgpib/.true./ + data no_board /.true./ data minmod/0/, maxmod/12/ data ilen/512/ data tmotbl/0,10,30,100,300,1000,3000,10000, 30000, 100000, @@ -220,6 +228,18 @@ program ibcon goto 151 endif modtbl(1,icount) = ibuf(1) +C Let's escape to a C function to check if we got a network-controlled +C device and process it, if that is the case --- the return value is either +C an index in the network-table, a zero to indicate a normal GPIB-board or +C '-1' to indicate an error + modtbl(4,icount) = check_network(ibuf, ireg) + write(*,*) 'Modtbl =',modtbl(4,icount) + if ( modtbl(4,icount).gt.0 ) then + goto 150 + else if ( modtbl(4,icount).lt.0 ) then + ierr = -3 + goto 1090 + end if C !! FIND COMMA AND MOVE DEVICE NAME INTO VARIABLE C !! IF THERE IS A COMMA, MOVE OPTION INTO VARIABLE icomma = iscn_ch(ibuf,4,ireg,',') @@ -250,6 +270,12 @@ program ibcon 151 continue C ndev = min0(icount,imaxdev) +C Check if we need to open the GPIB-board or if we only have +C network-controlled devices which have already been dealt with + do i=1,ndev + if ( modtbl(4,i).eq.0 ) no_board = .false. + end do + if ( no_board ) goto 1089 C call fs_get_idevgpib(idevgpib) if(ichcm_ch(idevgpib,1,'/dev/null ').eq.0) then @@ -269,7 +295,7 @@ program ibcon timlst(i)=it(1)+it(2)*100.+it(3)*60.d2+it(4)*3600.d2 oldcmd(i)=-1 enddo - kini = .true. + 1089 kini = .true. goto 1090 C C @@ -339,6 +365,24 @@ program ibcon if((imode.gt.4.and.imode.lt.9).or.imode.eq.11) then ilimit=min(ibuf(3),ibufln*2) endif +C +C Branch off to the network-handler, if the device is +C a network-controlled device and ignore the rest of this program +C A return of '1' from handle_network indicates that we have a reply +C we should pass back to the calling program, while a '0' indicates that +C a command was sent successfully, and '-1' that the communication failed + if ( modtbl(4,idev).gt.0 ) then + device = modtbl(4,idev) + status = handle_network(ibuf, ireg, device, ierr, ipcode) + if ( status.lt.0 ) goto 910 + if ( status.eq.1 ) then + nclrer = nclrer + 1 + idum = ichmv(ibuf2, 3, ibuf, 1, ireg) + ibuf2(1) = nadev !!MNEMONIC DEVICE NAME + call put_buf(iclasr, ibuf2, -ireg-2, ' ', ' ') + end if + goto 900 + end if C if(idev.gt.0) then call fc_rte_time(it,it(6)) diff --git a/ibcon/util_network.c b/ibcon/util_network.c new file mode 100644 index 000000000..ba3a20cf7 --- /dev/null +++ b/ibcon/util_network.c @@ -0,0 +1,572 @@ +/***************************************************************************** + * + * FILE: util_network.c + * + * Utility routines used when communicating with a network-controlled + * device. + * + * This procedure is part of the 'ibcon' program. + * + * The use of network-controlled devices is described in + * the 'check_network.c' routine. + * + * HISTORY + * + * who when what + * --------- ----------- ---------------------------------------------- + * lerner 26 Jul 2012 Original version (including a set of procedures + * written by Lars Pettersson) + * lerner 7 Feb 2013 Added user-specified port-number to enable + * communication with network-based devices + * lerner 27 Apr 2021 Adapted for inclusion in FS10 + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + + + +/***************************************************************************** + * + * Macros + * + *****************************************************************************/ + +#define MAX_ACTIONS 50 +#define MAX_SEQUENCES 20 + +#define CONNECT_TIMEOUT 5 + + + +/***************************************************************************** + * + * Global variables + * + *****************************************************************************/ + +/* Network device variables --- set up by 'check_network.c' */ + +extern char network_address[MAX_ACTIONS][16]; +extern char network_port[MAX_ACTIONS][8]; +extern int network_socket[MAX_ACTIONS]; + +/* Network action variables --- set up by 'check_network.c' */ + +extern char network_mnemonic[MAX_ACTIONS][3]; +extern char *network_sequence[MAX_ACTIONS][MAX_SEQUENCES]; +extern int network_check[MAX_ACTIONS][MAX_SEQUENCES]; +extern int network_device[MAX_ACTIONS]; + + + +/***************************************************************************** + * + * Subroutine declarations + * + *****************************************************************************/ + +/* Internal subroutines */ + +int connect_timeout(int sock, struct sockaddr *addr, socklen_t addrlen, + int seconds); +int open_connection(int *fd, char *host, char *host_port); +int recvtimeout(int socket, char *buf, size_t buf_len, unsigned int timeout); +int sendtimeout(int socket, char *buf, size_t buf_len, unsigned int timeout); + +/* Public subroutines */ + +int open_network(int box); +int close_network(int box); +int send_network(int device, char *buf, unsigned int timeout); +int read_network(int device, char *buf, size_t buf_len, unsigned int timeout); +int network_connected(int device); + +/* External subroutines */ + +int logit(); +int logita(); + + + +/***************************************************************************** + * + * connect_timeout + * + * this is an alternative 'connect' routine which allows you to set + * a time-out instead of having to wait many minutes on non-existing + * addresses as with the standard 'connect' + * + *****************************************************************************/ + +int connect_timeout(int sock, struct sockaddr *addr, socklen_t addrlen, + int seconds) { + + fd_set write_fd; + struct timeval timeout; + char string[512]; + unsigned long mode; + int status; + + /* Set up a non-blocking socket */ + + mode = 1; + + status = ioctl(sock, FIONBIO, &mode); + + if ( status != 0 ) { + snprintf(string, sizeof(string), "ioctl failed with error: %ld errno " + "= %d", status, errno); + logit(string, 0, NULL); + return(-1); + } + + /* Try the connect and return immediately if we get an error that is not + EINPROGRESS */ + + status = connect(sock, addr, addrlen); + + if ( status == -1 && errno != EINPROGRESS ) + return(-1); + + /* Reset the socket to blocking mode */ + + mode = 0; + + status = ioctl(sock, FIONBIO, &mode); + + if ( status != 0 ) { + snprintf(string, sizeof(string), "ioctl failed with error: %ld errno " + "= %d", status, errno); + logit(string, 0, NULL); + return(-1); + } + + /* Set up the time-out and the file descriptor list */ + + timeout.tv_sec = seconds; + timeout.tv_usec = 0; + + FD_ZERO(&write_fd); + FD_SET(sock, &write_fd); + + + /* Check if the socket is ready */ + + status = select(FD_SETSIZE, NULL, &write_fd, NULL, &timeout); + + if ( FD_ISSET(sock, &write_fd) ) + return(0); + + return(-1); +} + + + +/***************************************************************************** + * + * open_connection + * + * open the socket connection to a network device --- code written by + * Lars Pettersson with slight modifications + * + *****************************************************************************/ + +int open_connection(int *fd, char *host, char *host_port) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + char string[512]; + int opts, s; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Stream (TCP) socket */ + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + + if ((s = getaddrinfo(host, host_port, &hints, &result))) { + snprintf(string, sizeof(string), "getaddrinfo() => %s", + gai_strerror(s)); + logit(string, 0, NULL); + return -1; + } + + /* getaddrinfo() returns a list of address structures. Try each + * address until we successfully connect(2). If socket(2) (or + * connect(2)) fails, we (close the socket and) try the next + * address. + */ + for (rp = result; rp != NULL; rp = rp->ai_next) { + + *fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if (*fd == -1) { + snprintf(string, sizeof(string), "socket() => %s", + strerror(errno)); + logit(string, 0, NULL); + continue; + } + + if (connect_timeout(*fd, rp->ai_addr, rp->ai_addrlen, + CONNECT_TIMEOUT) != -1) { + break; /* Success */ + } + + snprintf(string, sizeof(string), "connect() => %s", strerror(errno)); + logit(string, 0, NULL); + + close(*fd); + } + + freeaddrinfo(result); /* No longer needed */ + + if (rp == NULL) { /* No address succeeded */ + snprintf(string, sizeof(string), "open_connection() => could not " + "connect, no address succeeded"); + logit(string, 0, NULL); + *fd = -1; + return -1; + } + + if ((opts = fcntl(*fd, F_GETFL)) < 0) { + snprintf(string, sizeof(string), "fcntl(serv_sock, F_GETFL) => %s", + strerror(errno)); + logit(string, 0, NULL); + (void) close(*fd); + return -1; + } + opts |= O_NONBLOCK; + if (fcntl(*fd, F_SETFL, opts) < 0) { + snprintf(string, sizeof(string), "fcntl(serv_sock, F_SETFL, opts) => " + "%s", strerror(errno)); + logit(string, 0, NULL); + (void) close(*fd); + return -1; + } + + return 0; +} + + + +/***************************************************************************** + * + * recvtimeout + * + * receive a message from a network device with a time-out --- code written + * by Lars Pettersson with slight modifications + * + *****************************************************************************/ + +/* --- receive characters, but do not wait forever --- + * + * Returns: + * number of bytes received on success + * 0 if remote side has closed the connection + * -1 on error + * -2 on timeout + * -3 on select error + */ + +int recvtimeout(int socket, char *buf, size_t buf_len, unsigned int timeout) +{ + fd_set fds; + struct timeval tv; + + /* set up the file descriptor set */ + FD_ZERO(&fds); + FD_SET(socket, &fds); + + /* set up the timeval struct for the timeout */ + tv.tv_sec = timeout; + tv.tv_usec = 0; + + /* wait until timeout or data received */ + switch (select(socket + 1, &fds, NULL, NULL, &tv)) { + case 0: + /* timeout */ + return -2; + case -1: + /* error */ + return -3; + default: + /* data available */ + break; + } + return recv(socket, buf, buf_len, 0); +} + + + +/***************************************************************************** + * + * sendtimeout + * + * send a message to a network device with a time-out --- code written + * by Lars Pettersson with slight modifications + * + *****************************************************************************/ + +/* --- send characters, but do not wait forever --- + * + * Returns: + * number of bytes received on success + * 0 if remote side has closed the connection + * -1 on error + * -2 on timeout + * -3 on select error + */ + +int sendtimeout(int socket, char *buf, size_t buf_len, unsigned int timeout) +{ + fd_set fds; + struct timeval tv; + + /* set up the file descriptor set */ + FD_ZERO(&fds); + FD_SET(socket, &fds); + + /* set up the timeval struct for the timeout */ + tv.tv_sec = timeout; + tv.tv_usec = 0; + + /* wait until timeout or data received */ + switch (select(socket + 1, NULL, &fds, NULL, &tv)) { + case 0: + /* timeout */ + return -2; + case -1: + /* error */ + return -3; + default: + /* data available */ + break; + } + return send(socket, buf, buf_len, 0); +} + + + +/* Wrappers to Lars Pettersson's procedures */ + +/***************************************************************************** + * + * open_network + * + * open a socket for communication with a network device --- return status + * is either '0' or '-1' + * + *****************************************************************************/ + +int open_network(int box) { + + char string[512]; + int status; + + snprintf(string, sizeof(string), "Opening socket to network device at %s " + "port %s ...", network_address[box], network_port[box]); + logit(string, 0, NULL); + + status = open_connection(&network_socket[box], network_address[box], + network_port[box]); + + if ( status < 0 ) { + logit("Failed opening socket!", 0, NULL); + network_socket[box] = -1; + } +// } else +// logit("Socket successfully opened", 0, NULL); + + return(status); +} + + + +/***************************************************************************** + * + * close_network + * + * close the socket used for communication with a network device, if it is + * open --- return status is always '0' + * + *****************************************************************************/ + +int close_network(int box) { + + if ( network_socket[box] > -1 ) + close(network_socket[box]); + + network_socket[box] = -1; + + return(0); +} + + + +/***************************************************************************** + * + * send_network + * + * send a message to the network device --- the message should be given + * in 'buf' --- 'timeout' is specified in integer seconds --- return + * status is either '0' or '-1' --- in case of any error, this procedure + * will send an error message to the log and close the socket + * communication + * + *****************************************************************************/ + +int send_network(int device, char *buf, unsigned int timeout) { + + char string[512]; + int box, status, length; + + box = network_device[device]; + +/* Verify that we are connected --- if we are not then there should be + a programming error, so we just bomb out of here */ + + if ( network_socket[box] == -1 ) { + logita("ERROR network device send called without previous connect", 0, "ib", + "P0"); + return(-1); + } + +/* Send the message to the network device */ + + length = snprintf(string, sizeof(string), "%s\n", buf); + + status = sendtimeout(network_socket[box], string, length, timeout); + +/* Return immediately if everything went fine */ + + if ( status == length ) + return(0); + +/* Report the error */ + + if ( status == -2 ) { + logita("WARNING time-out on network device send", 0, "ib", "P1"); + } else if ( status == -3 ) { + logita("WARNING select error on network device send", 0, "ib", "P2"); + logita(NULL, errno, "ib", "P2"); + } else if ( status == -1 ) { + logita("WARNING send error on network device send", 0, "ib", "P3"); + logita(NULL, errno, "ib", "P3"); + } else if ( status == 0 ) { + logita("WARNING connection closed on network device send", 0, "ib", "P4"); + } else if ( status < length ) { + snprintf(string, sizeof(string), "WARNING only %d bytes of %d sent to " + "network device", status, length); + logita(string, 0, "ib", "P5"); + } else { + logita("ERROR weird error on network device send", 0, "ib", "P6"); + } + +/* Close the socket to the network device */ + + close_network(box); + + return(-1); +} + + + +/***************************************************************************** + * + * read_network + * + * read a message from the network device --- the message will be stored + * in 'buf' and the length of 'buf' should be specified in 'buf_len' --- + * 'timeout' is specified in integer seconds --- return status is either + * '0' or '-1' --- in case of any error, this procedure will send + * an error message to the log and close the socket communication + * + *****************************************************************************/ + +int read_network(int device, char *buf, size_t buf_len, unsigned int timeout) { + + char string[256]; + int box, status; + + box = network_device[device]; + +/* Verify that we are connected --- if we are not then there should be + a programming error, so we just bomb out of here */ + + if ( network_socket[box] == -1 ) { + logita("ERROR network device read called without previous connect", 0, "ib", + "P0"); + return(-1); + } + +/* Read the message from the network device */ + + status = recvtimeout(network_socket[box], buf, buf_len, timeout); + +/* Process the message and return if we managed to read something --- + the processing consists of stripping any trailing new-line character + and adding a string termination character */ + + if ( status > 0 ) { + if ( buf[status-1] == '\n' ) + buf[--status] = '\0'; + if ( buf[status-1] == '\r' ) + buf[--status] = '\0'; + if ( status < buf_len ) + buf[status] = '\0'; + return(0); + } + +/* Report the error */ + + if ( status == -2 ) { + logita("WARNING time-out on network device read", 0, "ib", "P1"); + } else if ( status == -3 ) { + logita("WARNING select error on network device read", 0, "ib", "P2"); + logita(NULL, errno, "ib", "P2"); + } else if ( status == -1 ) { + logita("WARNING read error on network device read", 0, "ib", "P3"); + logita(NULL, errno, "ib", "P3"); + } else if ( status == 0 ) { + logita("WARNING connection closed on network device read", 0, "ib", "P4"); + } else { + logita("ERROR weird error on network device read", 0, "ib", "P6"); + } + +/* Close the socket to the network device */ + + close_network(box); + + return(-1); +} + + + +/***************************************************************************** + * + * network_connected + * + * returns a '1' if the socket for communication with the network device is + * open and '0' otherwise + * + *****************************************************************************/ + +int network_connected(int device) { + + int box; + + box = network_device[device]; + + if ( network_socket[box] == -1 ) + return(0); + + return(1); +} diff --git a/misc/general_ibcon.pdf b/misc/general_ibcon.pdf new file mode 100644 index 000000000..1902bfa10 Binary files /dev/null and b/misc/general_ibcon.pdf differ diff --git a/st.default/control/ibad.ctl b/st.default/control/ibad.ctl index 68e52fc77..48535899c 100644 --- a/st.default/control/ibad.ctl +++ b/st.default/control/ibad.ctl @@ -15,6 +15,32 @@ * converted to 100000000 (10 seconds) * other values rounded up to next longer usable value * +* OR if you want to use TCP/IP-based communication either directly or +* via a device like the Prologix GPIB-to-ethernet adapter: +* +* format YY=net,IP-address,port,/command1[/command2[/command3...]] +* where 'YY' is a 2-character identifier used to identify the device action +* 'net' is a literal used to specify that this is a network-based action +* 'IP-address' is the IP-address of the device in 'a.b.c.d' notation +* 'port' is the port number to use on the device +* the command sequence is the set of commands to send to the device +* +* The command sequence must start with the character to use as a separator +* to separate between the different commands in the command sequence. +* It has to be present even if there is only one command and can be any valid +* ASCII-character not used in any of the command(s). +* +* The identifier 'YY' is not used to identify a device but a certain action +* for that device. Different actions for the same device should be specified +* separately with each action having a unique individual identifier. +* +* You can mix devices connected via a GPIB-bus or via ethernet as you wish. +* IBCON will not look for a GPIB-card, if all devices in this file have been +* declared as ethernet devices. +* +* Further documentation and examples on how to use devices connected via +* ethernet are given in the document 'general_ibcon.pdf'. +* CA=dev3,0 * default K4 devices *R1=dev4,4 @@ -24,11 +50,11 @@ CA=dev3,0 *VB=dev27,4 *LB=dev28,4 *T1=dev29,4 -*supported keywords for general behavior (all devices): +*supported keywords for general behavior (all devices connected via a GPIB-bus): *no_untalk/unlisten_after - don't untalk & unlisten after read *no_online - no onlie when opening board or converter *no_write_ren - don't remote enable before write, board only *set_remote_enable - remote enable when opening board *no_interface_clear_board - interface clear when opening board *interface_clear_converter - interface clear when opening converter -*interface_clear_after_read - adds i/c after each read \ No newline at end of file +*interface_clear_after_read - adds i/c after each read