TCS Monitor Display

Technical Description

June 9, 1999

Jim Harwood

 

Introduction

Currently, the status display from the TCS goes from the TCS Forth software (device TV1, called with 1DSPLY) through a homebrew Q-bus interface to the proprietary 2-channel TV monitor display controller and from there over coax to an NTSC monitor, daisy-chained to other monitors spaced around the control room. The display format is of 16 lines of 32 characters each. Channel 1 is the on-line TCS display. Channel 2 is used for the Super Tech off line diagnostics system. (A few of the Super Tech tests also use Channel 1.) Channel 2 is a backup for the on-line system. In Forth, the second channel is named TV2 and is called with 2DSPLY.

I have prepared a replacement TCS status display system that runs on a Unix workstation (max) and gets its input through the workstation’s serial port from a newly installed serial port card in the Q-bus backplane. Only the one channel is supported. Super Tech will have to continue to use the old proprietary Channel 2. The TCS on-line software is a different version for this application, since the new serial port card is at a different bus address than the TV monitor display controller, so both devices can coexist on the backplane. From the point of view of the Forth software, the TCS monitor display output is still on device TV1, so there are no differences except for the changed bus address and special coding for ANSI standard cursor control. The proprietary controller is off line with this software.

The new serial port card on the Q-bus is only used for sending. For reasons of limited memory I didn’t configure the Forth software to accept characters through this port as if from a keyboard, only to send them.

The TCS display daemon tvmond executes in the workstation which has the serial port (max). Clients from different machines execute the client program (tcsdisplay) either by remote login to the IRTF network or locally, after downloading the client program, and receive stdout from that program on their Xterm window wherever in the world they are. Or, the client can log into the IRTF network remotely from a PC running HyperTerminal and execute tcsdisplay there. The only requirement is that the terminal emulator on the local machine be VT-102 compatible.

Xterm windows are VT-102 terminal emulators, the ANSI standard. We need VT-102 in order for cursor control to work correctly, a requirement since the TCS display is a template updated at a 5 hz rate.

See below for instructions on how to configure the system for a different processor, in case max goes down or changes its name.

 

Arrangement of modules

Besides a header file (tvmond.h) and make file (tcsmon.mak), the C files used in this system are the daemon tvmond.c, the executable that presents the display in an Xterm window tcsdisplay.c, and a group of functions that organize the different clients connected to the daemon client_array.c. The daemon services the serial port which is receiving characters from the TCS display, places the input characters in groups of 5 in a circular buffer, and connects through sockets to the clients from different machines, sending the characters to them from the circular buffer. Any number of Xterm clients can connect to the daemon. Of course, there will come a point at which there are so many clients that Unix can’t keep up, and the clients get farther and farther behind real time until max overloads and has to be restarted from login. On the fast processor that is currently max it will probably take some tens of clients at least to do this, more than would ever be connected in practicality.

All the pertinent files are in herschel: ~harwood/tcsdsply/ and the same on jiminy in my office. Other files used for this application are in my TCS comm link directory on hershel: ~harwood/skymap/commstuff/ which has files used by data acquisition communications support.

 

On-line location of executables

Daemon tvmond – At this writing, no system directory has been assigned. Currently in ~harwood/tcsdsply/. Needs to be set up so automatically loads at max boot time.

Client tcsdisplay – herschel, max, stefan: /usr/local/bin/

 

Communications

The serial port is read through an open() call in tvmond.c to the device TV_DEV (see tvmon.h). The port is conditioned for baud rate etc. by setting control flags in a structure and then doing an ioctl() using that structure. The port is then read by using read() with the file descriptor returned by the open() call. The serial port read function is done in non-canonical mode, so cursor control characters can be interpreted correctly.

Sockets are used for the interprocess communications. The daemon listens for a client on a well-known port number, TCS_MSG_PORT+1 (see skymap/commstuff/tcs_common.h). When connected, the daemon assigns the socket to a private, individual port number associated with a socket descriptor and then continues listening for connections on the well-known port. Up to 5 socket requests can simultaneously queue up at the well-known port number, though I wouldn’t expect queuing to be required very often, considering the random and infrequent nature of client connections. Once a socket connection between client and daemon is made, it is persistent until the client program is killed or its Xterm window is removed.

A global array of data relating to currently active client connections is maintained by the daemon. Each connection has a structure in the array consisting of its private socket descriptor, the network address of the client and the size of the address (both obtained by the accept() call in tvmond.c), and the circular buffer pointer for that client. This array of client structures is used by the daemon to keep track of where in the circular buffer each client is pointing and to output through the socket to the client the latest characters that arrived in the buffer. Eventually, I would like to see the names of the client workstations displayed also, from the information in the client data structures.

Whenever a client is terminated, the daemon knows of this by -1 being returned from a write() to that socket. The daemon then closes its side of the socket connection and sets the socket descriptor in the corresponding array structure to -1, the flag of an unused client structure. A new client can then make use of this structure without having to allocate more memory.

The client executable (tcsdisplay) communicates through sockets with two destinations; the TCS monitor display daemon tvmond as described, and also the standard command link to the TCS Forth system via the tcsd daemon at stefan. Currently, tcsdisplay uses this link to cause the TCS to refresh all the fields in the status display, useful after the display template has been redrawn, and to retrieve the TCS version information. As mentioned earlier, for reasons of limited memory the TCS Forth system has not been configured to accept command strings received through the newly installed serial port, so this alternate path to the TCS via tcsd is necessary.

 

Descriptions of files

client_array.c

Three utility functions are contained in this file. They are as follows:

client_alloc()
Allocates a batch of array memory for 10 (NALLOC in tvmond.h) client structures, using the malloc() function. If more than 10 structures are needed another call to this function is made, providing 10 more. The new entries are initialized with socket descriptors set to -1 and circular buffer pointers set to 0. There is no programmed upper limit to the number of allocated client structures. Called from tvmond during initialization and from client_add() (below).

client_add()
Assigns a new client to an available client array structure, setting the structure members including the new socket descriptor, which marks this structure as in use. If there is no free structure, calls client_alloc() (above) and uses one of the new structures provided by that call. The daemon tvmond calls this function when it accepts a new client socket.

client_del()
Returns a particular client array structure identified by a no longer needed socket descriptor to the available pool, by setting the structure file descriptor to -1. Called by the daemon when it detects a client socket closed.

 

tcsdisplay.c

This is the client executable that displays the TCS status updated at 5 hz. It can be run on any workstation that is permitted to connect a socket to the remote daemon on max. It must be run in an Xterm window. It can output to a PC running HyperTerminal in Windows 9x if the PC is able to log into a Unix workstation that is permitted to connect to the daemon.

First, tcsdisplay puts up the template, consisting of field identifiers arranged on the screen exactly as the hardware TV monitor did, in 16 lines of 32 characters. As part of the template() function, commands are sent to the TCS via the comm link daemon tcsd to refresh all the fields of the display and to retrieve the TCS software version number. Ordinarily, characters are not written by the TCS to unchanged fields, and unless refreshed, those fields would be left blank by the updating.

After writing the template, the function socket_open() is called to establish the remote socket connection to the tvmond daemon. Socket_open() is in socket_help.c in the skymap/commstuff directory.

The program then enters the main loop, after initializing parameters for the select() function. Execution blocks in select() until one of two sources is ready; a character string from the socket connection, or stdin from the keyboard. tcsdisplay can accept keyboard input while at the same time displaying the TCS status update. A menu of acceptable keys is displayed under the TCS 16-line display. If one of these keys is pressed along with Return, the corresponding action will be done. Right now two keys are active; ESC, which terminates the program, and "t" for renewing the template on the screen. All other keys are ignored.

If characters are available at the socket, they are read by a read() call and immediately output to stdout with a write() call. The character stream contains ANSI cursor control characters to place the information in the proper fields of the template.

A request to renew the template may well interrupt a multi-character cursor control sequence, so that when the template is finished, the next data to be displayed will be displayed in an unexpected location. To forestall this, the code that services characters from the socket skips input characters if template() was called until an ESC character is received, the start of the cursor control sequence.

 

tvmond.c

Run as a daemon on the computer receiving TCS status display characters through a serial port (max), tvmond.c continuously reads the serial character stream from the TCS and places it in a circular buffer. At the same time, the daemon services connected client sockets by sending unread sections of the circular buffer to them for display in their Xterm windows via tcsdisplay.

On entry to the main outer loop, a call is made to socket_setup_server() (skymap/commstuff/socket_help.c) to establish a listening socket with a well known port (TCS_MSG_PORT+1, defined in tcs_common.h). This port number is one more than the port number used for listening by the TCS command daemon, tcsd. If this socket can’t be created, socket_setup_server() returns with an error number related to the cause and the daemon outputs the appropriate message.

The serial port defined by TV_DEV (tvmond.h) is then opened as read-only, since the TCS can only send from its end on this circuit. The serial port is initialized through ioctl() calls with a baud rate of 9600, 8 bits, ignore BREAK, ignore parity errors, ignore handshake control lines (there aren’t any), and enable receiver (transmitter disabled by default). Non-canonical mode is the default (we aren’t using the ICANON flag).

NOTE: Currently, in my testing, I found that CTS/RTS handshake control was not necessary for this application. If it turns out that characters are actually getting lost, it may be necessary to substitute a Q-bus serial port card with handshake control for the current one, and to turn on the flag CRTS_IFLOW in the c_cflag member of the termios structure in the daemon (see listing). We have spare modem control cards, but if possible they should be reserved as spares for the Q-bus serial ports that require CTS/RTS control. Those spares are already configured as direct replacements for those serial ports, ready for immediate card swapping.

I set VMIN to NUM_CHARS (defined in tvmond.h), which is 5 characters per read operation. 5 provides some relief from the overhead of single character reads yet does not produce perceived bunching in the display, as happens with a larger number of characters per read operation. VTIME is set to zero to produce unlimited blocking. The combination of these parameters drives the input in bunches of 5 characters. If the character stream from the TCS stops, the daemon will just sit and wait in a select() call until it resumes

Signal handlers are defined to intercept certain Unix signals. It is important to include SIGPIPE in this list, for this is the signal that is automatically sent by Unix to the daemon when one of the attached client sockets is closed. Without handling this signal in tvmond.c, the daemon would be terminated by Unix. The handling is simply the flag SIG_IGN, which causes the signal to be ignored.

We now initialize for the upcoming select() call, where we will tell select() to watch for either of two possible inputs, either serial port ready or a new socket request from a client.

The inner loop is now entered, where the daemon does its work. We leave the loop only in case of error. At the start of the loop we wait in the select() call for input. If the input is the serial port, we have to read the pending characters from the TCS and send them to the attached clients. This is a real time operation, but Unix is not real time, so we have to code this part very carefully in order not to miss input characters. I make no attempt to change the Unix servicing priority of the daemon process. Careful buffering and attention to reading the serial port does the trick.

As soon as we find out through select() that serial port characters are ready, we read them into a holding array (spchar[]). The characters then get placed in the circular buffer, with proper attention to wraparound if necessary. We then go immediately into a for() loop around client structures. For every active client, defined as a structure whose fd member is not -1, we retrieve the circular buffer pointer for this client from the structure member buffout and use it to send the currently unread characters in the circular buffer to the client over the socket connection. If the socket write() call returns -1 instead of the number of characters transmitted, the client has terminated existence, so we set its structure member containing the file descriptor to -1, defining it as empty and available for reuse.

At this point, we do not go immediately to the next client structure for processing. Instead, we now check to see if the serial port has more characters while we were servicing the previous client. If so, we update the circular buffer and then go to the next client structure. This way, the serial port gets checked as frequently as possible, while the clients have to wait a bit more. No problem, because all input characters are buffered, so they will eventually get out to the clients. Interleaving reading the serial port with servicing the clients is the trick that permits an effective simulation of real time processing. Note that we don’t need to use a select() call for this check here; the serial port is polled instead. If the read() to the serial port is -1, it is not yet ready and we just carry on.

When all clients are serviced with the latest characters from the circular buffer, we loop back to waiting in the select() call at the start of the loop.

If the select() call awoke with a socket connection request from a new client instead of the serial port requesting service, the program gets the private file descriptor returned from the accept() call for the new client, finds an available client structure, and stores the new socket descriptor in the fd member of the client structure, making that client active. The circular buffer pointer for the new structure is zero, so all the characters in the circular buffer from the beginning to the current input pointer will be sent through the socket connection to the new client when the next character servicing is done, bringing that client immediately up to date.

If the accept() call fails, this loop is exited and the outer loop is started again, with the serial port initialization and socket server setup.

 

To reconfigure for a different host

The file tvmond.h contains a #define TV_HOST which has to be edited with the name of the new host. It also contains the #define TV_DEV which is the path of the serial port device.

After modifying these items, recompile in my directory using ~harwood/tcsdsply/tcsmon.mak. Two new executables will be built. Move tvmond to its proper place in the system as the daemon, and tcsdisplay to /usr/local/bin on the 3 main workstations for use by clients. Any clients that have previously downloaded tcsdisplay for use on their local machines will have to download this copy.

 

Things still to do

The error recovery of the program as it stands now is weak. If an error occurs with a client and the client exits, it only means that the user needs to start up tcsdisplay again. But if certain errors occur in the daemon, the operation of all clients probably will be affected. It will be quite inconvenient to have to restart the daemon and all the clients again. Following are some areas that could be addressed to make the daemon more robust.

  1. Currently, there is an outer loop around the whole main() of tvmond.c. This outer loop is entered and the inner processing loop exited with a break statement if a socket request from a new client fails. Going back around the outer loop will cause everything to be re-initialized, including the client structure array, so all the current clients will no longer be processed. What should happen if a client is unable to obtain a socket from the daemon is an error message sent to the client and processing continue in the daemon. If three of these errors occur in succession the daemon should close itself down along with the clients, the system rebooted, and the daemon restarted. [Such an error can only be caused by a Unix failure.]
  2. Currently, there is no error response in case of an error reading the serial port. My feeling on that is that the show must go on even if there is an intermittent read error once in a while when a character is available at the serial port. Who cares if a single character is corrupted in the continuous character stream. However, there may be a hardware or cable failure or other source of continuous error at the port. The program should check for three successive failures. If so, the serial port initialization should be redone and reads attempted again. If it occurs again, the daemon should emit an error message and exit.
  3. Within the inner while() loop of tvmond.c, execution blocks in a select() call. The program exits if select() failed. The program should probably try again 3 times and if it still fails, notify the operator to reboot the system.
  4. There are traps in the program for signals. Three of the traps cause the function abort_func() to execute. (The fourth, SIGPIPE, is programmed to be ignored.) Currently, all that happens for these 3 signals in abort_func() is that a message is output that the signal occurred. A study should be made of the sources of these signals and if we should be doing something else when they occur. Also, I don’t know exactly where execution in tvmond.c resumes after executing the abort_func() function as the result of the signal. Perhaps the program dies. Somebody should find out. (These traps are also included in the TCS daemon, tcsd.c, and are handled the same way. The same comments apply to tcsd.)

 

Enhancements to be considered

1. Whenever a new client is accepted by the daemon, the client’s internet address is saved in the client structure. This information can be used to show who is running TCS display windows. The display of users could be made either by default or on sending a command letter from the keyboard ("w" for "who", for example) for individual users. Another variation of this is to show somewhere in the display window just the current number of connections. Displaying anything in a fixed part of the screen requires cursor control by the client in the tcsdisplay program. In the file folder is a version of the listing with pencilled modifications to do cursor control in tcsdisplay.c. It follows the VT-102 (ANSI) standard.

2. The cursor in the TCS display is intrusive as it flits around the screen. The standard cursor, which is used in the Xterm window, is a black box. If the focus is outside the window, the box is white or transparent with an outline, which is a little better. The VT-102 terminal has a hardware setting that you can use to set the cursor as a line rather than a box. Unfortunately, this cursor option was not made downloadable for the VT-102, so it doesn’t seem to be included as an option for Xterm. If somebody figures out how to change the Xterm cursor to a line, it should be done.

NOTE: On a PC, a HyperTerminal window in VT-102 mode has the cursor we want, an underline. It may be that if you alter the terminfo file so the vt102 entry comes before xterm or ansi, the Xterm window may pick up the underline cursor. (Xterm picks the first terminal emulation it sees among certain closely related emulations in terminfo.)

 

 

Jim Harwood