record - An application for capturing data from an HP CMS (Merlin) monitor 1.0.0

File: <base>/src/record.c (80,180 bytes)
/* file: record.c	G. Moody	13 October 1993
			Last revised:	1 February 1996       	record 6.0
Record data received over serial lines from HP Component Monitoring System

Copyright (C) 1993, 1994, 1995, 1996 by George B. Moody.  Permission is granted
to reproduce and distribute copies of this program freely provided that this
copyright notice and permission statement are attached to all copies.

About this program
==================

This program determines what signals and measurements are available from a
Hewlett-Packard Component Monitoring System, and then logs all of the available
data to disk continuously until it is stopped by the user.  The monitor
contains up to eight plug-in modules for monitoring physiologic signals (ECG,
respiration, pressures, temperature, gases, etc.), and can be reconfigured by
the user by inserting or removing modules at any time without restarting the
monitor.  This program, however, determines only which signals and measurements
are available at the time it is started, and does not attempt to keep track of
modules added later.  Signals and measurements are considered available only if
the associated module and the transducer are connected and the parameter is
turned on; note that a signal or measurement need not be displayed on the
monitor screen to be available (in fact, this program cannot determine what
subset of available parameters is being displayed).

Compiling this program
======================

To compile this program using Borland or Turbo `make', copy the appropriate
versions of the external libraries `mecif.lib', `gfcl.lib', and `dbl.lib' (see
below) into this directory, and type
	make
See `makefile' in this directory for details on compilation.

This program has been successfully compiled using Turbo C/C++ 3.0 and Borland
C/C++ 4.0.  Earlier versions of these compilers may work as well, but will
require recompiling the external libraries as well as the program source.  It
should be fairly easy to port this program to Microsoft C (all of the libraries
it uses can be compiled by Microsoft C/C++ compilers without changes; this
program uses Borland/Turbo-specific functions for screen output, for far heap
memory allocation, and for checking the available disk space, but close
equivalents are provided with Microsoft C).  

This program makes use of three external libraries:

1. The MECIF library (mecif.lib), version A.01.10, available as Part
No. M1046-9220C (Dec. 1992) from Hewlett-Packard.  This library contains
functions that support serial communication between the PC and the HP CMS
monitor.  The package includes an MS-DOS diskette (containing the library
itself in source and binary forms, various header (.h) files, a demonstration
program, and documentation files), and a manual titled "HP Component Monitoring
System RS-232 Computer Interface Programming Guide (Option #J13)", referred to
below as the HP Guide.  The versions of the MECIF library on this diskette
include a compiled copy of "rs232g.c" (also on this diskette), and are the
preferred alternative to the version on the Hewlett-Packard diskette.  All
versions of the MECIF library are compiled using the large memory model;  this
diskette includes versions compiled for use with Turbo C/C++ 3.0 and Borland
C/C++ 4.0.

2. The Greenleaf CommLib library (gfcl.lib), version 4.0, available from
Greenleaf Software Inc., 16479 Dallas Parkway, Suite 570, Dallas, TX 75248
(telephone: +1 214 248 2561).  This library contains functions invoked by
`rs232g.c' (see above) that support low-level serial communications using many
different types of PC serial ports, including "smart" serial cards; this
program does not call any CommLib functions directly.  The package includes
MS-DOS diskettes (containing the library in source form, various header files,
and example programs), and a manual titled "Greenleaf CommLib 4.0 Reference",
referred to below as the CommLib manual.  The sources can be compiled by many
popular C compilers, including those from Borland and Microsoft; specify the
primary compiler to be used when ordering CommLib, and the package will also
include precompiled versions of the library for use with that compiler and
various memory models.  Be sure to use `gfcl.lib' (the large memory model
version) with this program.  CommLib is required in order to use the version of
the revised MECIF library on this diskette; it is not required in order to use
the Hewlett-Packard version.  Although executable programs built using CommLib
may be distributed without restriction, `gfcl.lib' may not be;  this diskette
therefore does not include a copy of `gfcl.lib'.

3. The DB library (dbl.lib), version 9.0 or later, provided on this diskette in
binary form for Turbo C/C++ 3.0 and Borland C/C++ 4.0, and available in binary
form for several other popular MS-DOS C compilers by anonymous FTP from
penelope.mit.edu.  This library includes functions that this program uses to
create files of received data in a compact, portable, binary format.  The DB
Software Package, available from the author (George B. Moody, MIT Room E25-505,
Cambridge, MA 02139; telephone: +1 617 253 7424), includes the DB library in C
source form for MS-DOS, UNIX, and the Macintosh, and is accompanied by a large
set of applications for physiologic signal processing, viewing, printing, and
editing recordings such as those produced by this program, as well as a manual
titled "ECG Database Programmer's Guide", referred to below as the DB Guide.
Be sure to use `dbl.lib' (the large memory model version) with this program.
Executable programs built using the DB library may be distributed provided
that the following notice is incorporated in all copies:
   Portions of this software are Copyright (C) Massachusetts Institute of
   Technology 1992, and are incorporated by permission.

It is possible to compile this program using the version of mecif.lib provided
on the HP diskette and without using CommLib, and to use it with the standard
PC serial ports (COM1 and COM2).  The use of the revised MECIF library provided
on this diskette, the CommLib library, and a "smart" serial card is strongly
recommended as an alternative, permitting reliable operation at top speed while
logging data to local drives.  Without the use of CommLib and a smart serial
card, this program is limited to logging data to network drives.  (Characters
arrive at 260 microsecond intervals on each of the 38400 baud serial lines.
Serial interrupts are locked out for several milliseconds during local disk
writes.  Without a smart card, characters that arrive while writing to the disk
are lost.  Although the MECIF protocol incorporates limited error detection,
there is no provision for error correction or retransmission of lost messages.
Lacking a smart serial card, it is possible to log data to a network drive,
since the network interface accepts data at PC bus speeds, avoiding the local
disk's interrupt latency.)  This operating mode has been tested using a 3Com
Etherlink III Ethernet interface card, with Sun PC-NFS networking software
used to access the network drives.

The versions of the revised MECIF library provided on this diskette have been
compiled for use with the DigiBoard PC/4e (or PC/8i) board, available from
DigiBoard, Inc., 6400 Flying Cloud Drive, Eden Prairie, MN 55344 (telephone: +1
612 943 9020).  Other serial devices supported by CommLib (hence the revised
MECIF library) include smart cards from Arnet and Star Gate; a one-line change
in `rs232g.c' is required in order to recompile a version of `mecif.lib' for
use with any of these cards.  The revised MECIF library also incorporates more
robust error-recovery that avoids the need to reboot the monitor in the event
of interrupted transmissions, a problem that occurs regularly when using the
version of mecif.lib from the HP diskette.

Running this program
====================

The file `readme.doc' in this directory is a user's manual for this program.
Refer to that file for details omitted in the brief description below.

To use this program:

1. Connect the HP monitor to the PC using standard serial printer cables (see
   the wiring diagram on page 2-2 of the HP Guide).  Up to four lines may be
   used.

2. Turn on both the monitor and the PC.

3. Run the program by typing
	record
   or
	record FILE
   (where FILE is the name of a configuration file such as `record.ini').

This program checks the available serial lines and sets the speeds of the PC's
ports to match those of the monitor.  The best results will be obtained using
the highest available speeds, permitting the maximum amount of data to be
retrieved.  If the total available bandwidth is insufficient to permit the
monitor to transmit all signals and measurements, this program attempts to
obtain as many signals as possible, and then as many measurements as possible
using the remaining bandwidth.  If this program is used routinely in a setting
in which the bandwidth is insufficient, it may be worth modifying the code to
permit the user to select which signals and measurements are to be recorded.

The monitor may have one or two RS232 serial cards installed in it (two are
necessary in order to provide suffficient bandwidth to acquire all signals in
most cases).  Each RS232 card provides two ports.  The lower ports are
designated ports 1 and 3, and support communications at 9600, 19200, and 38400
baud; the upper ports (2 and 4) are restricted to 9600 baud (or 19200 baud if
the lower port on the same card is not running at 38400 baud).  The TX/RX order
should be Low/High for all connected ports.  These settings cannot be changed
from the PC; refer to the HP Guide (pp. 2-4 to 2-9) for information on changing
them using the monitor's controls.

The following baud rate settings are recommended (choose the first set that
works from the list):

Port 1 Port 2  Port 3  Port 4
 38400	 9600	38400	 9600	* maximum bandwidth
 38400		38400	       ** maximum bandwidth with only two lines
 19200	19200	19200	19200	* use with modems or long cables if 38400 baud
				   can't be used
 38400	 9600			  maximum bandwidth with only one RS232 card
 38400				  maximum bandwidth with only one line

* Note that the revised MECIF library must be used in order to make use of
  three or four ports (the standard library supports only two).  In some
  monitor configurations, the use of three or four ports fails for unknown
  reasons (apparently not related to this program or to the PC hardware).  For
  this reason, the default configuration (**) uses only ports 1 and 3.

Any of the first three choices provides sufficient bandwidth in most cases.

If you are using a version of this program compiled for use with a DigiBoard
PC/4e or PC/8i serial card, connections should be made using ports P1, P2, P3,
and P4 (which should be configured as COM5, COM6, COM7, and COM8) of the
DigiBoard interface, and the DigiBoard driver (currently `xidos5.sys') should
be loaded.  Otherwise, use the PC serial ports COM1 and COM2 (depending on your
hardware, COM3 and COM4 may or may not be usable as well).  It makes no
difference which PC port is connected to which monitor port.

For details on setting options in this program, see the file `record.ini' in
this directory, which is a sample configuration file.  This program sets its
options using the first available source from the following:
   1. a file named on the command line (for example, `record myconfig.ini')
   2. a file named in the environment variable RCONFIG (for example, by the
      command `set RCONFIG=c:\record\myconfig.ini')
   3. a file named `record.ini' found in the same directory as `record.exe'
   4. the compiled-in defaults shown below
Thus a file named on the command line takes precedence over a file named
in RCONFIG, and so on.

About the output
================

Each time it runs, this program reads and updates an `index file'.  The index
file may be named in the configuration file (by default, it is `record.idx' in
the same directory as `record.exe').  The index file is a text file.  Each line
in it contains data for one run of `record'.  The first field of each line is
the run index number.

The run index number is the name of the directory in which the log files for
this run are collected (see below).  By default, this directory is placed in
c:\, but any other location may be specified using the configuration file.  If
space on the drive containing the log directory becomes nearly exhausted,
and a "spillover" directory (presumably on a different drive) has been
specified in the configuration file, `record' switches to that directory.  Only
one "spillover" directory may be specified;  if none is specified, `record'
does not use a "spillover" directory.  When almost all of the available space
has been used, `record' exits (it avoids writing on the last few blocks).

The log files contain the data retrieved from the monitor.  At regular
intervals (10 minutes by default), this program closes the current set of log
files and opens a new set; this strategy limits the amount of data lost if
there is an interruption in power or other system failure.  Each set of log
files is identified by a five-digit sequence number contained within the names
of the files.  The first three characters of the file name are the index number
(modulo 1000), and the next five are the sequence number (beginning with
00001).  (In the unlikely event that more than 99,999 sets of log files are
created, the index number is incremented and the files continue to be generated
in numerical order.)  The extension (`.dat', `.al', or `.nu') indicates
what type of data will be found in the file (sampled signals, annotations of
alarms and signal connections/disconnections, or numerics).  An ASCII header
(`.hea') file accompanies each set, and contains the date and time (from the
system clock) at which the files in the set were created, along with
information necessary to read the `.dat' and `.al' files using DB application
programs (such as `view', included in this directory).

Generally, log files of different types will have different lengths, but all
log files of any given type should have approximately the same length, with the
following exceptions:
  1. The last file of each type may be shorter than the others.
  2. If alarms or `inop' conditions occur, the corresponding `.al' and `.txt'
     files will be longer than the others.
  3. If a module was removed from the monitor during the run, the log files
     obtained during that period may be shorter than the others.
  4. Log files open when messages were lost may be shorter than the others.
Except for the first case, careful attention should be paid to any log files
that differ significantly in length from others of the same type.

This program also logs its run-time errors in an error log (by default,
`record.log', in the same directory as `record.exe').  The error log should
be consulted after each run. */

#include <stdio.h>
#include <string.h>
#include <time.h>

#include "meciflib.h"
#include "database.h"
#undef DIRECTORY	/* to avoid conflict with incompatible definition
			   in dir.h */

#include <dos.h>	/* ctrlbrk, getdfree */
#include <conio.h>     	/* cprintf, text colors, etc. */
#include <dir.h>	/* setdisk, chdir */
#include <alloc.h>	/* farmalloc */

#include "db.h"	       	/* DB library data structures and prototypes */
#include "ecgcodes.h"	/* Annotation types for DB library putann() function */

#define VERSION	"6.0"	/* version number of this program */

#define min(A,B)	(((A) < (B)) ? (A) : (B))

/* Prototypes for functions defined below. */
void init(int argc, char **argv);
void getdata(void);		
void cleanup(int exit_code);
void addcon(i_16 port, int com);
int findcon(i_16 port);
int try_com(int com, unsigned speed);
int cap(unsigned speed);
void assign_com(int index, int cost);
void deassign_com(int index, int credit);
int wave_priority(int index);
void make_timestamp(void);
void show_fixup_message(void);
void openlogerr(char *file_name);
void flush_buf(void);
int openlog(void);
void resynch(void);

void main(int argc, char **argv)
{
    init(argc, argv);	/* read config, open communications with monitor */
    getdata();		/* retrieve and record data from monitor */
    cleanup(0);		/* close files, reset hardware */
}

/* The `getconf' macro is used by init() (below) to check a line of input
   (already in `buf', defined within init) for the string named by getconf's
   first argument.  If the string is found, the value following the string
   (and an optional `:' or `=') is converted using sscanf and the format
   specifier supplied as getconf's second argument, and stored in the variable
   named by the first argument.  Finally, if debug is non-zero, getconf prints
   the name of the variable and its new value on the standard error output. */
#define getconf(a, fmt)	if (p = strstr(buf, #a)) { \
			    sscanf(p, #a "%*[=: ]" fmt, &a); \
			    if (debug) fprintf(stderr, #a " = " fmt "\n", a); \
			}

/* Configurable parameters. */
int debug = 0;		/* if non-zero, print debugging messages */
int error_show = 1;	/* if non-zero, display run-time errors */
unsigned file_duration = 600;/* length of log files in seconds */
int test_mode = 0;	/* if non-zero, skip interactive setup */
unsigned com_speed[5] = { 0U /* unused */, 38400U, 38400U, 0U, 0U };
			/* baud rates for serial lines */
unsigned com_errors[5];	/* error counts for serial lines */
char error_log[80];	/* name of error log */
char index_file[80];	/* name of index file */
char logdir[80];	/* the primary log file directory */
char log2dir[80];	/* the secondary log file directory */
char notice[5][80];	/* a message of up to 5 lines to be posted on-screen
			   while this program is running */

/* Files used by this program (excluding those created by the DB library). */
FILE *debugfp = stderr;	/* destination for debugging messages */
FILE *errorlogfp;	/* run-time error log */
FILE *indexfp;		/* file containing index numbers and patient data */
FILE *nufp;		/* numeric data log file */

/* Other global variables. */
char *pname;	/* the name of this program, extracted from argv[0] */
int indexno;	/* index (identification) number for current run */
long seqno;	/* file sequence number + 10^5 * (indexno mod 1000) */
int porta;	/* com number of fastest port (1, 2, 3, or 4) */
int portb;	/* com number of 2nd-fastest port (0 if only 1 port active) */
int portc;	/* com number of 3rd-fastest port (0 if <3 ports active) */
int portd;	/* com number of slowest port (0 if <4 ports active) */
int bwtoohigh;	/* non-zero if bandwidth requirements of available data exceed
		   the capacity of available connections */
int initializing; /* 1 while executing in init, 0 afterwards */
int nresp = 0;	/* number of responses to tune requests received */
int nwaves = 0;	/* number of cooked wave message types in tune lists */
long ncw, nnu, nws, nal;/* numbers of cooked wave, numeric, wave support, and
			   alarm/inop messages logged */
int nmeas = 0;	/* number of numeric measurement types to be logged */
char numap[48][8];	/* labels of numeric measurements to be logged */
int drive, altdrive;	/* drive numbers for drives containing logdir and
			   log2dir (2 = c:, 3 = d:, etc.) */
int error_flag;	/* if non-zero, an error occurred since last openlog() */
double clusters_per_hour;     /* estimated rate of consumption of disk space */

/* The date and time strings below can be set by invoking make_timestamp()
   whenever it's necessary to use one of them. */
char date_string[12];
char time_string[10];

/* The following arrays are indexed by logical com numbers (1, 2, 3, or 4).
   If using the revised MECIF library, these are mapped to PC COM numbers in
   "rs232g.c";  otherwise, they correspond to the physical ports COM1, COM2,
   COM3, and COM4. The first element in each array (with subscript 0) is
   unused. */
int bandwidth[5];	/* expected rate of data received (bytes/sec) */
int capacity[5];	/* capacity of connections (bytes/sec) */
int connected[5];	/* non-zero for active connections */
i_16 cid[5];		/* MECIF con ids */
i_16 pid[5] = { -1, -1, -1, -1, -1 }; 	/* MECIF port ids */
LibTuneList tune_list[5];	/* see below */

/* 
About the tune lists
====================

A LibTuneList consists of a pointer to an array of MsgIdTyps, and a counter
indicating the length of the array; it specifies what parameters are to be
retrieved over a given com port.  tune_list (declared above) is an array of
LibTuneLists, one for each com port (as noted above, tune_list[0] is unused).

The tune_list_msg_id array (below) is an array of MsgIdTyps, but the members of
tune_list address only subarrays of it.  The entire tune_list_msg_id array is a
list of all of the parameters that are supported by the monitor, obtained using
the par_list_req function of the MECIF library.

For any given monitor configuration, only a subset of the supported parameters
is actually available.  The init function below determines which parameters are
available and then rearranges tune_list_msg_id so that all parameters to be
retrieved using a given com port occupy contiguous subarrays within the larger
array; it then sets the tune_list[] pointers and counters to address these
subarrays.

The three arrays that follow tune_list_msg_id are not part of the tune lists
themselves, but they are organized in parallel with tune_list_msg_id and
contain additional information about the parameters.
*/

#define NMID	128
MsgIdTyp tune_list_msg_id[NMID];/* list of parameters supplied by monitor */
char tune_list_ascii_id[NMID][12];  /* parameter names (e.g. "PRESS 1") */
char tune_list_label[NMID][12];	/* assigned names (e.g. "ABP") */
char tune_list_active[NMID];	/* status (0: not available; 1 or 2: to be
				   retrieved via com1 or com2;  -1: available
				   but not being retrieved) */

/* Wave priorities.  These are used to determine which signals are to be
recorded if there is insufficient bandwidth to record all of them.  A signal
with a numerically high priority will be recorded in preference to one of
lower priority. */

int ecg_priority[4] = { 0 /* unused */, 10, 10, 10 };
int press_priority[7] = {0 /* unused */, 5, 5, 5, 3, 3, 3 };
int resp_priority = 7;
int pleth_priority = 4;
int co2_priority = 6;
int other_priority = 2;

/* DB library-related variables. */
#define BSIZE 1024
int *vbuf;		/* circular buffer of samples to be recorded */
char recname[9];      /* name of current DB record (seqno, as 8-digit ASCII) */
int nsig;		/* number of signals being recorded */
int tspf;		/* total number of samples per frame */
int smap[MAXSIG];	/* frame offset of first sample in each signal */
long record_length;	/* length of generated DB records in samples */
MsgIdTyp fsmap[MAXSIG];	/* fsmap[i] is the MsgId of cooked wave and wave
			   support messages related to signal i, used by
			   findsig() to get the value of i given a message. */
DB_Siginfo si[MAXSIG];	/* si[i] contains information for signal i (filled in
			   by init() and used by openlog() when creating the
			   `.dat' and `.hea' files) */
/*
About the patient data
======================

This information is acquired from the user by init, logged in the index file
together with the index number, and displayed on-screen for the duration of the
recording.  To make its use by nursing staff as painless as possible, this
program does not enforce the use of any particular formats for these strings;
they are needed only to reduce the possibility of losing track of the source of
the recording, and they should be verified and corrected if necessary by
comparison with the patient's chart at the time data from the chart are
entered.  At that point, it is appropriate to enforce the use of standard
formats.
*/

char patient_name[80];	/* as on the patient's chart */
char patient_mrn[20];	/* medical record number */
char start_date[20];	/* date at beginning of recording */
char start_time[20];	/* time at beginning of recording (may not match
			   system time -- needed later to synchronize nurse's
			   notes, etc., with data gathered by this program) */

/* Function `init' reads the configuration file, opens the communications
   ports, determines what information is to be retrieved from the monitor,
   acquires the patient data, and initializes the reading loop. */
void init(argc, argv)
int argc;
char *argv[];
{
    char buf[256], *cfname = NULL, ini_file[80], *p, *getenv();
    FILE *config;
    int a, c, i, j, k, npar;
    LibRet *status;
    MsgIdTyp tm;
    ParList *plp;

    initializing = 1;
    /* Extract the name of this program from argv[0]. */
    pname = argv[0] + strlen(argv[0]);
    while (pname >= argv[0] && *pname != '\\' && *pname != ':') {
	if (*pname == '.')
	    *pname = '\0';		/* strip off extension */
	pname--;
    }
    pname++;

    vbuf = (int *)farmalloc((long)sizeof(int)*(long)BSIZE*(long)16);
    if (vbuf == NULL) {
	fprintf(stderr, "%s: insufficient memory\n", pname);
	exit(1);
    }

    /* Show the opening screen. */
    clrscr();
    textbackground(GREEN);
    textcolor(YELLOW);
    for (i = 1; i < 26; i++) {
	gotoxy(1, i);
	cprintf("                                        "
		"                                        ");
    }
    gotoxy(5, 9);
    cprintf("PC interface to the Hewlett-Packard Component Monitoring System");
    gotoxy(30, 11);
    cprintf("%s %s (%s)", pname, VERSION, __DATE__);
    textcolor(WHITE);
    gotoxy(7, 13);
    cprintf("Author: George B. Moody");
    gotoxy(15, 14);
    cprintf("Harvard-MIT Division of Health Sciences and Technology");
    gotoxy(15, 15);
    cprintf("MIT Room E25-505A");
    gotoxy(15, 16);
    cprintf("Cambridge, MA 02139 USA");
    gotoxy(15, 18);
    cprintf("Telephone: +1 617 253 7424");
    gotoxy(15, 19);
    cprintf("Internet: george@mit.edu");
    gotoxy(5, 21);
    cprintf("Portions of this software are Copyright (C) Massachusetts"
	    " Institute of");
    gotoxy(5, 22);
    cprintf("Technology 1992, and are incorporated by permission.");

    gotoxy(1, 24);
    cprintf("Press a key to begin: ");
    if (getch() == '\033') {
	clrscr();
	textbackground(BLACK);
	textcolor(WHITE);
	for (i = 1; i < 26; i++) {
	    gotoxy(1, i);
	    cprintf("                                        "
		    "                                        ");
	}
	exit(0);
    }
    clrscr();

    /* Generate the default names of the index file, the error log, and the
       configuration file from argv[0].  Note that the `.exe' has been stripped
       off already (above). */
    sprintf(index_file, "%s.idx", argv[0]);
    sprintf(error_log, "%s.log", argv[0]);
    sprintf(ini_file, "%s.ini", argv[0]);

    /* Set the default initial part of the log file directory name.  The
       index number will be appended to this string. */
    strcpy(logdir, "c:\\");

    /* Set the default initial part of the alternate ("spillover") directory
       name.  On a system with 2 disk drives, this might be "d:\\";  it's
       best to set this in record.ini, however, so that the configuration is
       more readily visible to the user. */
    strcpy(log2dir, "");

    /* Get the name of the configuration file, from the command line ... */
    if (argc > 1) cfname = argv[1];
    /* ... or from the environment. */
    else if ((cfname = getenv("RCONFIG")) == NULL)
	cfname = ini_file;
    /* Unless its name is missing or empty, open the configuration file. */
    if (cfname && *cfname) {
	char *p;
	if ((config = fopen(cfname, "rt")) == NULL) {
	    gotoxy(2, 12);
	    textcolor(YELLOW);
	    cprintf("%s: can't read configuration file %s\n", pname,
		    cfname);
	    gotoxy(1, 25);
	    textcolor(WHITE);
	    exit(1);
 	}
	/* Read the configuration file a line at a time. */
	while (fgets(buf, sizeof(buf), config)) {
	    /* Skip comments (empty lines and lines beginning with `#'). */
	    if (buf[0] == '#' || buf[0] == '\n') continue;
	    /* Set parameters.  Each `getconf' below is executed once for
	       each non-comment line in the configuration file. */
	    getconf(debug, "%d");
	    getconf(com_speed[1], "%u");
	    getconf(com_speed[2], "%u");
	    getconf(com_speed[3], "%u");
	    getconf(com_speed[4], "%u");
	    getconf(file_duration, "%u");
	    getconf(test_mode, "%d");
	    getconf(error_log, "%s");
	    getconf(error_show, "%d");
	    getconf(index_file, "%s");
	    getconf(logdir, "%s");
	    getconf(log2dir, "%s");
	    getconf(notice[0], "%s");
	    getconf(notice[1], "%s");
	    getconf(notice[2], "%s");
	    getconf(notice[3], "%s");
	    getconf(notice[4], "%s");
	    getconf(ecg_priority[1], "%d");
	    getconf(ecg_priority[2], "%d");
	    getconf(ecg_priority[3], "%d");
	    getconf(press_priority[1], "%d");
	    getconf(press_priority[2], "%d");
	    getconf(press_priority[3], "%d");
	    getconf(press_priority[4], "%d");
	    getconf(press_priority[5], "%d");
	    getconf(press_priority[6], "%d");
	    getconf(resp_priority, "%d");
	    getconf(pleth_priority, "%d");
	    getconf(co2_priority, "%d");
	    getconf(other_priority, "%d");
	}
    }
    fclose(config);

    /* Open the error log if specified. */
    if (*error_log && (errorlogfp = fopen(error_log, "at")) == NULL) {
	gotoxy(2, 12);
	textcolor(YELLOW);
	cprintf("%s: can't open error log `%s'\n", pname, error_log);
	textcolor(WHITE);
	gotoxy(1, 25);
	exit(1);
    }
    
    /* Save debugging messages in error log if specified. */
    if (debug && errorlogfp)
	debugfp = errorlogfp;

    /* Initialize the communications port(s). */
    for (i = 1; i < 5; i++) {
	while (com_speed[i] &&
	       try_com(i, com_speed[i]) == 0 &&
	       try_com(i, com_speed[i] = 38400U) == 0 &&
	       try_com(i, com_speed[i] = 19200U) == 0 &&
	       try_com(i, com_speed[i] = 9600U) == 0) {
	    gotoxy(2, 10);
	    textcolor(YELLOW);
	    cprintf("Please check the com%d cable.", i);
	    textcolor(WHITE);
	    gotoxy(2, 12);
	    cprintf("Press <Enter> to try com%d again,", i);
	    gotoxy(2, 13);
	    cprintf("   or <Esc> to continue without using com%d: ", i);
	    if (getch() == '\033')
		com_speed[i] = 0;
	    else {
		clrscr();
		gotoxy(2, 8);
		cprintf("Testing com%d connection (please wait) ...", i);
	    }
	}
	clrscr();
    }

    if (com_speed[1] == 0 && com_speed[2] == 0 &&
	com_speed[3] == 0 && com_speed[4] == 0) {
	cleanup(0);
	gotoxy(2, 12);
	textcolor(YELLOW);
	cprintf("This program cannot run, "
		"because serial communications cannot be established.");
	textcolor(WHITE);
	gotoxy(1, 25);
	exit(1);
    }

    capacity[0] = porta = portb = portc = portd = 0;
    for (i = 1; i < 5; i++)
	capacity[i] = cap(com_speed[i]);
    for (i = 1; i < 5; i++)
	if (capacity[i] > capacity[porta]) porta = i; 
    for (i = 1; i < 5; i++)
	if (capacity[i] > capacity[portb] && i != porta) portb = i; 
    for (i = 1; i < 5; i++)
	if (capacity[i] > capacity[porta] && i!=porta && i!=portb) portc = i; 
    for (i = 1; i < 5; i++)
	if (capacity[i]>capacity[porta]&&i!=porta&&i!=portb&&i!=portc)portd=i; 

    cid[porta] = connect_req(pid[porta], SRC_ParServer, 60, 0, DISABLE);
    addcon(cid[porta], porta);
    if (portb) {
	cid[portb] = connect_req(pid[portb] ,SRC_ParServer, 60, 0, DISABLE);
	addcon(cid[portb], portb);
    }
    if (portc) {
	cid[portc] = connect_req(pid[portc], SRC_ParServer, 60, 0, DISABLE);
	addcon(cid[portc], portc);
    }
    if (portd) {
	cid[portd] = connect_req(pid[portd], SRC_ParServer, 60, 0, DISABLE);
	addcon(cid[portd], portd);
    }

    /* Once we begin making requests, rx_mecif must be called frequently to
       receive the monitor's responses (at least when they are expected), or
       communications will fail at some point.

       Originally, I tried to use detune_all_req and disconnect_req after
       gathering the data needed to construct the tune lists, so that the
       patient data could be gathered interactively without having to call
       rx_mecif during that time.  I didn't succeed, apparently because using
       these functions doesn't reset the MECIF library to its original state,
       and subsequent attempts to reopen communications fail as a result.

       I finally tried disabling mirror requests from the monitor to the PC
       entirely (as specified by the penultimate parameter in the connect_req
       argument lists above), and using a long interval between PC-initiated
       mirror requests.  This allows me not to call rx_mecif during interactive
       input.

       Why not simply do the interactive input first?  I think this would be
       a poor design from the user's perspective, since (1) the user has to
       wait to see if the recording process starts properly in any case, and
       (2) if it doesn't start properly, this program must be restarted (since,
       as noted above, the MECIF library doesn't seem to provide a way of
       returning to its initial state) and the data entered by the user would
       have to be re-entered. */

    gotoxy(2, 10);
    if (par_list_req(cid[porta]) != SUCCESS) {
	/* This seems to happen only if the response of the monitor to a
	   previous par_list_req was not completely received, as might happen
	   if this program was run previously and interrupted (see below).
	   There doesn't seem to be anything we can do about this problem
	   from the PC end.  This problem has not been observed when using the
	   the revised MECIF library (see above). */
	cprintf("This computer cannot obtain data from the"
		" monitor until the monitor has been");
	gotoxy(2, 11);
	cprintf("restarted.  Please restart the monitor"
		" and run this program again.");
	cleanup(0);
	exit(1);
    }

    cprintf("Identification of the signals and measurements"
	    " available from the HP");
    gotoxy(2, 11);
    cprintf("monitor usually takes about 30 seconds."
	    "  If this computer pauses for a");
    gotoxy(2, 12);
    cprintf("longer period, press any key to interrupt it and try again.");
    gotoxy(2, 14);
    cprintf("Identifying available parameters (please wait) ...");
    while (1) {
	status = rx_mecif();	/* read a message */
	if (status->cmd == PAR_LIST_RSP) {
	    cprintf(".");
	    if ((status->ret).pret == NULL) continue;
	    /* wait until entire list has been received */
	    plp = (ParList *)((status->ret).pret);
	    break;
	}
	/* It seems like a really bad idea to interrupt this loop, since
	   doing so while the monitor is answering a PAR_LIST_REQ may result
	   in leaving the monitor unwilling to communicate further until
	   it's restarted (see error message above).  But if we can't
	   interrupt this loop, we have to restart the PC *and* the monitor
	   when the monitor doesn't answer, and it takes longer to restart the
	   PC than the monitor.  So we trust the user not to interrupt the
	   loop except when the monitor doesn't answer. */
	if (kbhit()) {
	    show_fixup_message();
	    cleanup(0);
	    exit(1);
	}
    }
    npar = plp->num_of_par;
    for (i = 0; i < npar; i++) {
	tune_list_msg_id[i] = plp->ident[i].msg_id;
	if (nwaves == 0 && i > 0 &&
	    tune_list_msg_id[i].MsgType != tune_list_msg_id[0].MsgType)
	    nwaves = i;
	strncpy(tune_list_ascii_id[i], plp->ident[i].ascii_id, 8);
    }

    if (debug) {
	fprintf(debugfp, "Available parameters:");
	for (i = 0; i < npar; i++)
	    fprintf(debugfp, "%u %12s", plp->ident[i].msg_id.SourceNo,
		    plp->ident[i].ascii_id);
    }

    /* Determine what signals and measurements are active on the monitor.
       Begin by checking the wave support messages. */
    for (i = 0; i < nwaves; i += 10) {
	tune_list[porta].num_tune = min(nwaves-i, 10);
	tune_list[porta].msg_id_ptr = &tune_list_msg_id[i];
	nresp = 0;
	if (tune_lib_req(cid[porta], &tune_list[porta], SINGLE) != SUCCESS)
	    cleanup(1);
	do {
	    status = rx_mecif();	/* read a message */
	    if (kbhit()) {
		show_fixup_message();
		cleanup(0);
		exit(1);
	    }
	} while (status->cmd != SINGLE_TUNE_RSP || status->ret.pret == NULL);
	/* The following message loop is here so that the responses to the
	   single requests can be received before requesting the next set.
	   It's not obvious how to be sure that all of the responses have
	   been received, particularly for parameters other than wave support
	   and numerics. The rx_lib_* functions below update nresp, but it
	   appears that only rx_lib_ws and rx_lib_nu actually get invoked
	   as a result of messages received here. 

	   According to the HP Guide (page 5-17), the MECIF library is
	   "sensitive" to the frequency of rx_mecif calls.  It's not clear what
	   this means, but the 50 msec delay in this loop seems to keep the
	   library happy.  A longer delay risks losing messages, and no delay
	   at all seems to have a similar effect for reasons unknown. */
	for (j = 0; j < 40 && nresp < tune_list[porta].num_tune; j++) {
	    status = rx_mecif();
	    delay(50);
	}	    
	if (j == 40) textcolor(YELLOW);
	cprintf(".");
	if (j == 40) textcolor(WHITE);
    }

    /* Skip the cooked wave messages for now (the wave support messages tell
       us what we need to know about these).  Continue by checking the
       remaining parameters available from the monitor. */
    for (i = 2*nwaves; i < npar; i += 10) {
	tune_list[porta].num_tune = min(npar-i, 10);
	tune_list[porta].msg_id_ptr = &tune_list_msg_id[i];
	nresp = 0;
	if (tune_lib_req(cid[porta], &tune_list[porta], SINGLE) != SUCCESS)
	    cleanup(1);
	do {
	    status = rx_mecif();	/* read a message */
	    if (kbhit()) {
		show_fixup_message();
		cleanup(0);
		exit(1);
	    }
	} while (status->cmd != SINGLE_TUNE_RSP || status->ret.pret == NULL);
	for (j = 0; j < 40 && nresp < tune_list[porta].num_tune; j++) {
	    status = rx_mecif();
	    delay(50);
	}	    
	if (j == 40) textcolor(YELLOW);
	cprintf(".");
	if (j == 40) textcolor(WHITE);
    }

    clrscr();
    if (debug) {
	gotoxy(1,1);
	cprintf("nwaves = %d\r\n", nwaves);
	cprintf("  Press any key to continue:");
	getch();
	clrscr();
    }

    gotoxy(41, k = 1);
    k++;
    cprintf("Measurements:");
    for (i = 2*nwaves; i < 127; i++) {
	int cost;

	if (tune_list_msg_id[i].MsgType == tune_list_msg_id[2*nwaves].MsgType){
	    if (strstr(tune_list_ascii_id[i], "PRESS") ||
		strstr(tune_list_ascii_id[i], "NBP"))
		cost = 55;
	    else
		cost = 39;
	    if (tune_list_active[i])
		assign_com(i, cost);	/* may modify tune_list_active[i] */
	    if (tune_list_active[i] > 0)
		strcpy(numap[nmeas++], tune_list_label[i]);
	    if (k >= 12) {
		gotoxy(1, 24);
		cprintf("Press any key to continue: ");
		getch();
		gotoxy(1, 24);
		clreol();
		for (k = 2; k < 12; k++) {
		    gotoxy(41, k);
		    clreol();
		}
		k = 2;
	    }
	    if (debug || tune_list_active[i]) {
		gotoxy(41, k++);
		cprintf("%8s -> %12s (%d)",
			tune_list_ascii_id[i],
			*tune_list_label[i]? tune_list_label[i]: "*** ??? ***",
			tune_list_active[i]);
	    }
	}
	else if ((tune_list_msg_id[i].SourceId == SRC_AlMgr) &&
	    (tune_list_msg_id[i].ChannelId == CHA_AlText)) {
	    assign_com(i, 61);	/* cost is zero except when an alarm occurs */
	    gotoxy(2, 10);
	    cprintf("Alarms  (%d)", tune_list_active[i]);
	}
	else if ((tune_list_msg_id[i].SourceId == SRC_AlMgr) &&
	    (tune_list_msg_id[i].ChannelId == CHA_InText)) {
	    assign_com(i, 63);	/* cost is zero except when an "inop" occurs */
	    gotoxy(20, 10);
	    cprintf("Inops  (%d)", tune_list_active[i]);
	}
#if 0
	else if ((tune_list_msg_id[i].SourceId == SRC_AlMgr) &&
	    (tune_list_msg_id[i].ChannelId == CHA_AlStat))
	    assign_com(i, 13);
#endif
    }

    gotoxy(2, 1);
    cprintf("Signals:");
    /* This code is not ideal, since it relies on the (observed) sequence of
       parameters:
       	       0 - nwaves-1	wave support
          nwaves - 2*nwaves-1	cooked waves
        2*nwaves - 127		numerics, other
       A better solution would be to check the last three characters in the
       ascii_id strings to determine the parameter type and to match the
       wave support and cooked wave parameters, rather than relying on the
       observed order.
    */
    for (i = 0, k = 2; i < nwaves; i++) {
	int cost;
	if (strstr(tune_list_ascii_id[i], "ECG"))
	    cost = 1409;	/* 33 (wave support) + 43*32 (cooked wave) */
	else
	    cost = 641;		/* 33 (wave support) + 19*32 (cooked wave) */
	if (tune_list_active[i]) {
	    assign_com(i, cost); /* i = index of the wave support message */

	    while (tune_list_active[i] < 0) {
		/* Not enough bandwidth available -- check if a lower priority
		   waveform can be "bumped" from the list. */
		int l, m, p, pmin;

		for (l = 0, m = i, pmin = wave_priority(i); l < i; l++) {
		    if (tune_list_active[l] > 0 &&
			(p = wave_priority(l)) < pmin) {
			pmin = p;
			m = l;
		    }
		}
		if (m < i) {
		    int credit;
		    if (strstr(tune_list_ascii_id[m], "ECG"))
			credit = 1409;
		    else
			credit = 641;
		    fprintf(errorlogfp, "deassigning %s\n",
			    tune_list_ascii_id[m]);
		    deassign_com(m, credit);
		    assign_com(i, cost);
		}
		else
		    break;
	    }
	}
    }
    for (i = 0, k = 2; i < nwaves; i++) {
	/* The next statement assigns the cooked wave to the same state as
	   its associated wave support. */
	tune_list_active[i+nwaves] = tune_list_active[i];
	if (k >= 12) {
	    gotoxy(1, 24);
	    cprintf("Press any key to continue: ");
	    getch();
	    gotoxy(1, 24);
	    clreol();
	    for (k = 2; k < 12; k++) {
		gotoxy(2, k);
		clreol();
	    }
	    k = 2;
        }
	if (debug || tune_list_active[i]) {
	    gotoxy(2, k++);
	    cprintf("%8s -> %12s (%d)",
		    tune_list_ascii_id[i],
		    *tune_list_label[i] ? tune_list_label[i] : "*** ??? ***",
		    tune_list_active[i]);
	}
    }
    record_length = file_duration * 125L;

    /* Warn if the communications capacity is exceeded.
       *** Code to allow the user to choose which parameters to retrieve
           should go here ***  */
    if (bwtoohigh) {
	textcolor(YELLOW);
	gotoxy(2, 15);
	cprintf("Bandwidth insufficient to receive signals or measurements"
		" marked with (-1)");
	textcolor(WHITE);
    }

    /* Set up the DB library signal information structures for the signals
       to be retrieved.  These are already in sorted order in the tune_list_*
       arrays. */
    for (i = nsig = 0; i < nwaves && nsig < MAXSIG; i++) {
	char *p;

	if (tune_list_active[i] <= 0) continue;
	fsmap[nsig] = tune_list_msg_id[i];
	for (p = &tune_list_label[i][SPI_LABEL_LENGTH-2]; *p == ' '; p--)
	    ;
	si[nsig].desc = tune_list_label[i];
	*(p+1) = '\0';	/* discard trailing spaces */
	if (strstr(tune_list_ascii_id[i], "ECG"))
	    si[nsig].spf = 4;	/* 4 samples per frame per ECG signal */
	else
	    si[nsig].spf = 1;	/* 1 sample per frame per other signal */
	smap[nsig] = tspf;
	tspf += si[nsig].spf;
	si[nsig].fmt = 212;	/* we'll pack 2 12-bit samples into 3 bytes */

	/* The lines below are a crude attempt to set rough calibrations for
	   some common signals, based on observed values while running in
	   `demo' mode. I haven't figured out how to get accurate calibration
	   data from the monitor yet. */
	if (strcmp(si[nsig].desc, "ABP") == 0) {
	    si[nsig].gain = 20;
	    si[nsig].baseline = -1500;
	    si[nsig].units = "mmHg";
	}
	else if (strcmp(si[nsig].desc, "PAP") == 0) {
	    si[nsig].gain = 75;
	    si[nsig].baseline = -1500;
	    si[nsig].units = "mmHg";
	}
	else
	    si[nsig].gain = 2000;

	si[nsig++].adcres = 12;
	/* The `fname' member of the structure will be set by openlog(). */
    }

    /* Construct the final tune lists.  First we sort the tune_list_* arrays
       by the port number.  This sort must be non-destructive (i.e., the
       relative order of elements with the same port numbers must not be
       changed). */
    for (i = 0; i < NMID; i++) {
	int swaps;

	for (j = 1, swaps = 0; j < NMID-i; j++)
	    if (tune_list_active[j-1] > tune_list_active[j]) {
		k = tune_list_active[j-1];
		tune_list_active[j-1] = tune_list_active[j];
		tune_list_active[j] = k;
		tm = tune_list_msg_id[j-1];
		tune_list_msg_id[j-1] = tune_list_msg_id[j];
		tune_list_msg_id[j] = tm;
		swaps++;
	    }
	if (swaps == 0) break;	/* sorting is completed when no more swaps */
    }

    /* Now we scan the sorted arrays to determine the first element belonging
       to each list, and how many elements belong to each list. */
    for (i = j = k = 0; i < NMID; i++)
	if (tune_list_active[i] > j) {
	    tune_list[j].num_tune = i - k;    /* set length of previous list */
	    j = tune_list_active[i];
	    tune_list[j].msg_id_ptr = &tune_list_msg_id[k = i];
	       /* ... and the pointer to the beginning of this list */
	}
    tune_list[j].num_tune = i - k;

    for (i = 1; i < 5; i++) {
	gotoxy(1, i+10);
	if (com_speed[i])
	    cprintf("COM%d: %u baud, capacity %d bytes/second"
		" (%d used for %d item%s, %d free)",
		i, com_speed[i], capacity[i], bandwidth[i],
		tune_list[i].num_tune,
		tune_list[i].num_tune == 1 ? "" : "s",
		capacity[i] - bandwidth[i]);
	else
	    cprintf("COM%d: not available", i);
    }

    /* Open the index file and determine the next index number. */
    if (indexfp = fopen(index_file, "rt")) {
	while (fgets(buf, sizeof(buf), indexfp)) {
	    if (buf[0] == '#') continue;
	    sscanf(buf, "%d", &indexno);
	}
	indexno++;
	fclose(indexfp);
    }

    /* Create the log file directory (or directories). */
    p = logdir + strlen(logdir);
    if (*(p-1) != '\\') *p++ = '\\';	/* make sure the prefix ends in `\' */
    sprintf(p, "%03d", indexno);
    if (mkdir(logdir) < 0) {	/* can't create it, try incrementing indexno */
	sprintf(p, "%03d", ++indexno);
	if (mkdir(logdir) < 0) {
	    gotoxy(1, 24);
	    textcolor(YELLOW);
	    cprintf("%s: can't create log directory `%s'", pname, logdir);
	    textcolor(WHITE);
	    cleanup(2);
	}
    }
    if (log2dir[0]) {
	p = log2dir + strlen(log2dir);
	if (*(p-1) != '\\') *p++ = '\\';
	sprintf(p, "%03d", indexno);
	if (mkdir(log2dir) < 0) {
	    gotoxy(1, 24);
	    textcolor(YELLOW);
	    cprintf("%s: can't create log directory `%s'", pname, log2dir);
	    textcolor(WHITE);
	    cleanup(3);
	}
    }

    /* Change to the drive and directory specified by logdir. */
    if (logdir[1] == ':') {	/* logdir includes drive letter */
	if ('a' <= logdir[0] && logdir[0] <= 'z') drive = logdir[0] - 'a';
	else if ('A' <= logdir[0] && logdir[0] <= 'Z') drive = logdir[0]-'A';
	setdisk(drive);
    }
    else
	drive = getdisk();
    chdir(logdir);
    if (log2dir[0] != '\0' && log2dir[1] == ':') {
	/* log2dir includes drive letter */
	if ('a' <= log2dir[0] && log2dir[0] <= 'z') altdrive = log2dir[0]-'a';
	else if ('A'<=log2dir[0]&& log2dir[0]<='Z') altdrive = log2dir[0]-'A';
    }
    else
	altdrive = drive;

    /* Create the first set of log files. */
    seqno = (long)(indexno % 1000) * 100000;
    error_flag = 0;

    /* Prepare to update the index file. */
    if ((indexfp = fopen(index_file, "at")) == NULL) {
	gotoxy(1, 24);
	textcolor(YELLOW);
	cprintf("%s: can't open index file `%s'\n", pname, index_file);
	textcolor(WHITE);
	cleanup(5);
    }

    if (debug) {
	gotoxy(1, 24);
	cprintf("Press any key to continue: ");
	getch();
	clrscr();
    }

    if (test_mode) {	/* non-interactive, do not gather patient data */
	make_timestamp();
	strcpy(start_date, date_string);
	strncpy(start_time, time_string, 5);
	strcpy(patient_name, "Test mode");
	strcpy(patient_mrn, "11 22 33 44");
	clrscr();
    }
    else {
	gotoxy(2, 16);
	cprintf("Please enter the patient's name and"
		" medical record number as they appear on");
	gotoxy(2, 17);
	cprintf("the patient's chart, today's date, and the"
		" current time as shown by the wall");
	gotoxy(2, 18);
	cprintf("clock.  Press <Enter> after each entry.");
	gotoxy(2, 20);
	cprintf("Patient's name: ");
	gotoxy(2, 21);
	cprintf("Medical record #: ");
	gotoxy(2, 22);
	cprintf("Today's date: ");
	gotoxy(2, 23);
	cprintf("Current time: ");
	textcolor(YELLOW);
	gotoxy(20, 22);
	cprintf("%s", start_date);
	gotoxy(20, 23);
	cprintf("%s", start_time);
	textcolor(WHITE);
	_wscroll = 0;

	do {
	    char ch;

	    textcolor(YELLOW);
	    buf[0] = sizeof(patient_name);
	    do {
		gotoxy(20, 20);
		/* Let the user bail out here (by pressing the <escape> key)
		   without making a recording.  This is useful if (for example)
		   the desired signals are not available from the monitor, and
		   the monitor needs to be reconfigured before making the
		   recording. */
		ch = getch();
		if (ch == '\033') {
		    cleanup(0);
		    chdir("\\");
		    rmdir(logdir);
		    if (log2dir[0])
			rmdir(log2dir);
		    exit(1);
		}
		else
		    ungetch(ch);
		cgets(buf);
	    } while (buf[1] == '\0' && patient_name[0] == '\0');
	    if (buf[1]) {
		strcpy(patient_name, buf+2);
		gotoxy(20+strlen(patient_name), 20);
		clreol();
	    }
	    buf[0] = sizeof(patient_mrn);
	    do {
		gotoxy(20, 21);
		cgets(buf);
	    } while (buf[1] == '\0' & patient_mrn[0] == '\0');
	    if (buf[1]) {
		strcpy(patient_mrn, buf+2);
		gotoxy(20+strlen(patient_mrn), 21);
		clreol();
	    }
	    buf[0] = sizeof(start_date);
	    gotoxy(20, 22);
	    clreol();
	    make_timestamp();
	    strcpy(start_date, date_string);
	    cprintf("%s", date_string);
	    gotoxy(20, 22);
	    cgets(buf);
	    if (buf[1]) strcpy(start_date, buf+2);
	    buf[0] = sizeof(start_time);
	    gotoxy(20, 23);
	    clreol();
	    make_timestamp();
	    strcpy(start_time, time_string);
	    cprintf("%s", time_string);
	    gotoxy(20, 23);
	    cgets(buf);
	    if (buf[1]) strcpy(start_time, cgets(buf));
	    gotoxy(2, 25);
	    textcolor(WHITE);
	    cprintf("Is this information correct? "
		    "(press Y or N, and <Enter>): ");
	    textcolor(YELLOW);
	    do {
		/* Offer a last chance to bail out here (by pressing the
		   <escape> key) without making a recording. */
		ch = getch();
		if (ch == '\033') {
		    cleanup(0);
		    chdir("\\");
		    rmdir(logdir);
		    if (log2dir[0])
			rmdir(log2dir);
		    exit(1);
		}
		else
		    ungetch(ch);
		buf[0] = 2;
		cgets(buf);
	    } while (buf[2] != 'y' && buf[2] != 'Y' &&
		     buf[2] != 'n' && buf[2] != 'N');
	    gotoxy(2, 25);
	    clreol();
	} while (buf[2] != 'y' && buf[2] != 'Y');
	textcolor(WHITE);
	clrscr();
    }

    if (errorlogfp)
	fprintf(errorlogfp, "%s %s Started test %03d\n", date_string,
		time_string, indexno);

    /* Update and close the index file. */
    fprintf(indexfp, "%03d\t%s\t%s\t%s\t%s\n", indexno, patient_mrn,
	    start_date, start_time, patient_name);
    fclose(indexfp);

    setbasetime(NULL);
    if (openlog() < 1) {
	gotoxy(1, 24);
	textcolor(YELLOW);
	cprintf("%s: can't create log files", pname);
	textcolor(WHITE);
	cleanup(4);
    }

    if (tune_lib_req(cid[porta],&tune_list[porta],CONTINUOUS) != SUCCESS)
	cleanup(6);
    do {
	status = rx_mecif();
	if (kbhit()) cleanup(7);
    } while (status->cmd != TUNE_RSP || status->ret.pret == NULL);

    if (portb && tune_list[portb].num_tune) {
	if (tune_lib_req(cid[portb], &tune_list[portb], CONTINUOUS) !=
	    SUCCESS)
	    cleanup(8);
	do {
	    status = rx_mecif();
	    if (kbhit()) cleanup(9);
	} while (status->cmd != TUNE_RSP || status->ret.pret == NULL);
    }

    if (portc && tune_list[portc].num_tune) {
	if (tune_lib_req(cid[portc], &tune_list[portc], CONTINUOUS) !=
	    SUCCESS)
	    cleanup(10);
	do {
	    status = rx_mecif();
	    if (kbhit()) cleanup(11);
	} while (status->cmd != TUNE_RSP || status->ret.pret == NULL);
    }

    if (portd && tune_list[portd].num_tune) {
	if (tune_lib_req(cid[portd], &tune_list[portd], CONTINUOUS) !=
	    SUCCESS)
	    cleanup(12);
	do {
	    status = rx_mecif();
	    if (kbhit()) cleanup(13);
	} while (status->cmd != TUNE_RSP || status->ret.pret == NULL);
    }

    gotoxy(1, 1);
    cprintf("Patient's name:   %s", patient_name);
    gotoxy(1, 2);
    cprintf("Medical record #: %s    ID: %03d", patient_mrn, indexno);
    gotoxy(1, 3);
    cprintf("Recording since:  %s on %s", start_time, start_date);
    gotoxy(1, 5);
    cprintf("Signals and measurements are now being recorded"
	    " by this computer.  To end");
    gotoxy(1, 6);
    cprintf("the recording at any time, hold down the `Ctrl'"
	    " key and type `D'.  Doing so");
    gotoxy(1, 7);
    cprintf("has no effect on the patient's bedside monitor.");

    /* Post the user message, if any. */
    for (i = 0; i < 5; i++)
	if (notice[i][0]) {
	    for (j = 0; notice[i][j]; j++)
		if (notice[i][j] == '_') notice[i][j] = ' ';
	    gotoxy(1, 9+i);
	    cprintf("%s", notice[i]);
	}

    gotoxy(1, 14); cprintf("Messages");
    gotoxy(1, 15); cprintf("WS: ");
    gotoxy(1, 16); cprintf("CW: ");
    gotoxy(1, 17); cprintf("NU: ");
    gotoxy(1, 18); cprintf("AL/IN: ");
    if (nsig > 0) {
	gotoxy(19, 14);
	cprintf("Signals");
    }
    if (nmeas > 0) {
	gotoxy(40, 14);
	cprintf("Measurements");
    }
    gotoxy(1, 25);
    cprintf("%s %s", pname, VERSION);

    initializing = 0;
}

int c_break(void)
{
    return (1);
}

/* Function `getdata' contains the reading loop (the code that retrieves
   data from the monitor and records it in disk files). */
void getdata()
{
    ctrlbrk(c_break);	/* ignore keyboard interrupts */
    while (1) {
	rx_mecif();	/* read a message */
	if (kbhit()) {
	    if (getch() == '\04') {	/* control-D */
		gotoxy(20, 25);
		cprintf("Interrupt -- exiting.");
		gotoxy(1, 24);
		break;
	    }
	}
    }
}

/* Function `cleanup' closes files and resets the hardware just before
   this program exits. */
void cleanup(int exit_code)
{
    int i, j;
    LibRet *status;

    for (j = 1; j < 5; j++)
	if (cid[j] && tune_list[j].num_tune) {
	    detune_all_req(cid[j], 0);
	    for (i = 0; i < 25; i++) {
		status = rx_mecif();
		if (status->cmd == DETUNE_ALL_REQ) break;
	    }
	}
    for (j = 1; j < 5; j++)
	if (cid[j]) disconnect_req(cid[j]);

    for (j = 1; j < 5; j++)
	if (pid[j] != -1) close_port(pid[j]);

    /* Close the log files, if any. */
    if (*recname) {
	flush_buf();
	newheader(recname);
	dbquit();
    }
    if (nufp) fclose(nufp);

    if (exit_code && errorlogfp) {
	make_timestamp();
	fprintf(errorlogfp, "%s %s Fatal error %d\n", date_string,
		time_string, exit_code);
    }
    else if (!initializing && errorlogfp) {
	make_timestamp();
	fprintf(errorlogfp,
	   "%s %s   Ended test %03d (WS: %ld, CW: %ld, NU: %ld, AL/IN: %ld)\n",
		date_string, time_string, indexno, nws, ncw, nnu, nal);
    }
    if (errorlogfp) fclose(errorlogfp);
    if (exit_code) {
	clrscr();
	gotoxy(2, 10);
	cprintf("This program cannot run without corrective action.");
	gotoxy(2, 11);
	cprintf("Please report error code %d%c.", exit_code, 'A'+ exit_code);
	gotoxy(1, 25);
	exit(exit_code);
    }
    gotoxy(1, 25);
}

/* Check the physical connection between a com line and the monitor. mirror_req
   sends a message to the monitor and returns immediately. If the connection is
   functional, rx_mecif will receive a MIRROR_RSP;  if not, the MECIF library
   calls rx_error with code NO_RSP_MIR_REQ after two seconds have elapsed, and
   rx_error clears connected[]. */
int try_com(int com, unsigned speed)
{
    LibRet *status;
    int debug_save = debug;
    i_16 temp_cid;

    if ((pid[com] = init_rs232port(com-1, speed)) == BAUDRATE_WRONG ||
	(pid[com] == PORT_WRONG) ||
	(pid[com] == NOT_ENOUGH_MEMORY)) {
	pid[com] = -1;
	return (connected[com] = 0);	/* failure */
    }

    debug = 0;
    connected[com] = 1;
    addcon(temp_cid = mirror_req(0, com-1, 0, 0), com);
    while (connected[com]) {
	status = rx_mecif();	/* read a message */
	if (status->cmd == MIRROR_RSP) {
	    if (debug = debug_save) 
		fprintf(stderr,	"com%d (port %d) initialized at %u baud\n",
			com, pid[com], speed);
	    return (1);	/* success */
	}
    }

    debug = debug_save;
    disconnect_req(temp_cid);
    /* ^^^ This disconnect_req is critically important!  If a mirror request
       is issued on a line that (a) is not properly configured, and (b) does
       not get disconnected, then continuous mode (but not single) requests on
       a properly configured line will fail (actually, the request itself
       succeeds, but none of the requested messages get delivered).  In this
       program, this can happen if the disconnect_req is omitted and one of
       the com lines is not connected to the monitor. This looks like a MECIF
       library bug. */
    close_port(pid[com]);
    pid[com] = -1;
    return (0);	/* failure */
}

/* This function determines the capacity in bytes per second of a com line,
   given its baud rate.  The capacities are based on those given in the table
   on page 4-8 of the HP Guide, which are stated to include allowances for
   escape sequences in the transmissions.  (It is not clear if these numbers
   represent bytes per second, as stated on page 4-8, or bytes per 1.024
   seconds, as stated in the example on page 4-9.)  Note that the full
   capacity of a 9600 baud line is 920 bytes/second.  If port 1 or 3 on the
   monitor is running at 38400 baud, however, the HP Guide (p. 4-9) states
   that the capacity of the second port (2 or 4) on the same card is limited
   to 600 bytes/second.  This program assumes that any port running at 9600
   baud is subject to this restriction (a reasonable assumption, since there
   is no other reason to lower the baud rate in this way).
 */
int cap(unsigned speed)
{
    switch (speed) {
      case 9600U:  return (600);
      case 19200U: return (1850);
      case 38400U: return (3750);
      default: return (0);
    }
}

/* assign_com chooses a port to be used to receive a given message type.
   It does this by finding a port with sufficient bandwidth for the message.
   This is not quite a "knapsack problem", because we don't particularly
   want to use all of the available bandwidth of any given port, since
   the bandwidth calculations are only approximate.  The goal is rather
   to leave some unused bandwidth for each port if possible, ideally in
   equal amounts. */

void assign_com(int i, int cost)
{
    int j, k, l;

    /* Find the port with the greatest remaining capacity. */
    for (j = 1, k = l = 0; j < 5; j++)
	if (capacity[j] - bandwidth[j] > k) {
	    k = capacity[j] - bandwidth[j];
	    l = j;
	}

    /* If this port has sufficient bandwidth, accept the assignment. */
    if (k >= cost) {
	tune_list_active[i] = l;
	bandwidth[l] += cost;
    }

    /* Otherwise, set flags to indicate bandwidth exceeds capacity. */
    else {
	tune_list_active[i] = -1;
	bwtoohigh = 1;
    }
}

/* Reverse the effects of assign_com. */
void deassign_com(int i, int credit)
{
    /* Find the port to which the parameter has been assigned, and credit
       it with the cost. */
    if (tune_list_active[i] > 0) {
	bandwidth[tune_list_active[i]] -= credit;
	tune_list_active[i] = -1;
    }
}

/* Determine the priority of the waveform indexed by i. */
int wave_priority(int i)
{
    char *p = tune_list_ascii_id[i], *q;

    if (q = strstr(p, "ECG-CH")) {
	fprintf(errorlogfp, "pr(%s) = %d\n", p, ecg_priority[q[6] - '0']);
	return (ecg_priority[q[6] - '0']);
    }
    else if (q = strstr(p, "PRESS")) {
	fprintf(errorlogfp, "pr(%s) = %d\n", p, press_priority[q[6] - '0']);
	return (press_priority[p[6] - '0']);
    }
    else if (strstr(p, "RESP")) {
	fprintf(errorlogfp, "pr(%s) = %d\n", p, resp_priority);
	return (resp_priority);
    }
    else if (strstr(p, "PLETH")) {
	fprintf(errorlogfp, "pr(%s) = %d\n", p, pleth_priority);
	return (pleth_priority);
    }
    else if (strstr(p, "CO2")) {
	fprintf(errorlogfp, "pr(%s) = %d\n", p, co2_priority);
	return (co2_priority);
    }
    else {
	fprintf(errorlogfp, "pr(%s) = %d\n", p, other_priority);
	return (other_priority);
    }
}

#define CONTABSIZE 100

struct {
    i_16 con;	/* MECIF connection id */
    char com;	/* associated com number (1 or 2) */
} contab[CONTABSIZE];	/* as connections are opened, addcon adds entries
			   to this table.*/
int ncons = 0;	/* number of connections (entries in contab) */

void addcon(i_16 con, int com)
{
    int i;

    for (i = 0; i < ncons; i++)
	if (contab[i].con == con) {
	    contab[i].com = (char)com;
	    return;
	}
    if (ncons < CONTABSIZE) {
	contab[ncons].con = con;
	contab[ncons].com = (char)com;
	ncons++;
    }
    if (debug)
	fprintf(stderr, "Connection %d is via com%d\n", con, com);
}

int findcon(i_16 con)
{
    int i;

    for (i = 0; i < ncons; i++)
	if (contab[i].con == con) return ((int)contab[i].com);
    return (0);
}

void make_timestamp()
{
    struct tm *now;
    time_t t, time();
    static char *monthname[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
				   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

    t = time((time_t *)NULL);    /* get current time from system clock */
    now = localtime(&t);
    (void)sprintf(date_string, "%2d %s %d",
		  now->tm_mday, monthname[now->tm_mon], now->tm_year+1900);
    (void)sprintf(time_string, "%02d:%02d:%02d",
		  now->tm_hour, now->tm_min, now->tm_sec);
}

void show_fixup_message()
{
    gotoxy(2, 16);
    cprintf("Interrupt -- exiting.");
    gotoxy(2, 17);
    cprintf("Please wait 10 seconds to allow the computer to"
	    " correct the communication");
    gotoxy(2, 18);
    cprintf("problem, then start this program again."
	    "  If the problem persists on a");
    gotoxy(2, 19);
    cprintf("second attempt, please restart this computer"
	    " using the reset button on its");
    gotoxy(2, 20);
    cprintf("front panel and then try once more.  "
	    "If that fails, or if the list of signals");
    gotoxy(2, 21);
    cprintf("and measurements is incomplete, please restart the monitor.");
    gotoxy(1, 25);
}

void openlogerr(char *file_name)
{
    if (errorlogfp) {
	make_timestamp();
	fprintf(errorlogfp, "%s %s Could not create `%s'\n",
		date_string, time_string, file_name);
    }
}

/* The string `altext' describes the most recent alarm reported by the monitor
   (see rx_lib_al() below for details).  Since `altext' is used as an `aux'
   string for an annotation (see the DB Guide), its first byte is a byte count
   of the remainder of the string, not including the terminating null. */
static char altext[80];

/* About sample numbers:

Samples are identified by `sample numbers' (defined as the number of samples
of the same signal that preceed the given sample).  The records collected by
this program are organized as segments to be collected into composite records.
Within this program, an `absolute sample number' refers to the sample number
within the composite record (i.e., the number of samples from the first sample
of the first segment to the sample immediately before the given sample), and
a `relative sample number' refers to the sample number within the current
segment only.
*/

static long t[MAXSIG];	/* absolute sample numbers of most recently received
			   samples for each signal */
static long t0;		/* absolute sample number of sample 0 of the current
			   segment */

int openlog()
{
    static char fname[100];
    int i, status = 0;
    unsigned used;
    static unsigned avail, maxused;
    struct dfree free;
    static DB_Anninfo ai = { "al", WRITE };

    seqno++;
    if (*recname) {
	if (error_flag) {
	    resynch();
	    error_flag = 0;
	}
	newheader(recname);
	dbquit();
	getdfree(drive+1, &free);
	used = avail - free.df_avail;
	gotoxy(4, 22 + (drive == altdrive));
	cprintf("%4.1f hours", free.df_avail / clusters_per_hour);
	if (used > maxused) maxused = used;
	if (free.df_avail < 2*maxused) {
	    if (drive != altdrive) {	/* switch to alternate drive */
		setdisk(altdrive);
		drive = altdrive;
		getdfree(drive+1, &free);
		avail = free.df_avail;
		chdir(log2dir);
	    }
	    else {	/* exit, not enough space to continue */
		if (errorlogfp) {
		    make_timestamp();
		    fprintf(errorlogfp, "%s %s File space exhausted\n",
			    date_string, time_string);
		}
		cleanup(0);
		exit(0);
	    }
	}
	else
	    avail = free.df_avail;
    }
    else {	/* first call -- perform once-only initialization */
	dbquiet();
	getdfree(drive+1, &free);
	avail = free.df_avail;
	gotoxy(1, 21 + (drive == altdrive));
	cprintf("Disk space:");
	gotoxy(1, 22 + (drive == altdrive));
	clusters_per_hour= 1.15e7/((double)free.df_bsec*(double)free.df_sclus);
	cprintf("%c: %4.1f hours", drive+'A',
		free.df_avail / clusters_per_hour);
	if (drive != altdrive) {
	    getdfree(altdrive+1, &free);
	    gotoxy(1, 23);
	    cprintf("%c: %4.1f hours", altdrive+'A',
		    free.df_avail/clusters_per_hour);
	}
	for (i = 1; i < 5; i++) {
	    if (com_speed[i]) {
		gotoxy(1, 18+i);
		cprintf("com%d err:", i);
		gotoxy(10, 18+i);
		cprintf("%5d", com_errors[i] = 0U);
	    }
	}
    }
    setsampfreq(125.0);
    sprintf(recname, "%08ld", seqno);
    sprintf(fname, "%s.dat", recname);
    for (i = 0; i < nsig; i++)
	si[i].fname = fname;
    if (osigfopen(si, nsig) < nsig)
	openlogerr(fname);
    else
	status = 1;
    setbasetime(NULL);
    sprintf(fname, "%s.txt", recname);
    if (nufp) fclose(nufp);
    if ((nufp = fopen(fname, "wt")) == NULL)
	openlogerr(fname);
    else {
	fprintf(nufp, "%s\t%s\n", timstr(0L), fname);
	status += 2;
    }
    if (annopen(recname, &ai, 1) < 0) {
	sprintf(fname, "%s.al", recname);
	openlogerr(fname);
    }
    else {
	/* Reset the alarm text string so that any pending alarm is recorded
	   in the new annotation file (altext[0] = length, altext[1-7] =
	   "ALARM: ", altext[8-80] = description of alarm). */
	altext[8] = '\0';
	t0 = t[0];	/* sample number of the first sample in this segment,
			   relative to the beginning of the first segment */
	status += 4;
    }
    return (status);
}

/* Callbacks.  The functions below are called by the MECIF library, not
   directly by the functions above. */

/* rx_error is the error handler called by the MECIF library. */
void rx_error(u_16 error_code, i_16 con)
{
    int com = findcon(con);
    static char buf[80], *error;

    /* For definitions (only) of the error codes below, see meciflib.h.
       For cryptic interpretations, see the HP Guide, pp. 5-23 and 5-24. */
    switch (error_code) {
      case WINDOW_SIZE_OVERFLOW:	/* from the monitor */
	error = "Window size overflow";
	/* This apparently means that the monitor did not receive all of
	   the data transmitted to it by the PC. */
	break;
      case TX_BUFFER_OVERFLOW:		/* from the monitor */
	error = "TX buffer overflow";
	/* This apparently means that the PC did not receive all of the data
	   transmitted to it by the monitor. */
	break;
      case HW_HANDSHAKE_MALFUNCTION:	/* from the monitor */
	error = "Hardware handshake failed";
	/* ??? */
	break;
      case MPB_FIFO_OVERFLOW:		/* from the monitor */
	error = "MPB FIFO overflow";
	/* Possibly means the PC didn't receive all of the data sent by the
	   monitor, or else that the monitor couldn't transmit everything
	   requested. ??? */
	break;
      case PARITY_ERROR:		/* from the monitor */
	error = "Parity error";
	/* The HP Guide suggests that this refers to data received by the
	   monitor. ??? */
	break;
      case NOTIFIC_ABORTED:		/* from the monitor */
	error = "Notification aborted";
	/* related somehow to lifetick timeout (see below) ??? */
	/* possibly due to physical connection failure */
	break;
      case LIFETICK_TIMEOUT:		/* from the MECIF library */
	error = "Lifetick timeout";
	/* Connection was closed because the monitor didn't answer two
	   lifetick requests. */
	/* possibly due to physical connection failure */
	connected[com] = 0;
	break;
      case LIFETICK_FAILED:		/* from the MECIF library */
	error = "Lifetick failed";
	/* Connection was closed because the monitor answered a lifetick
	   request incorrectly. */
	/* possibly due to physical connection failure */
	connected[com] = 0;
	break;
      case NO_RSP_MIR_REQ:		/* from the MECIF library */
	error = "Connection failed (mirror request)";
	/* possibly due to physical connection failure */
	connected[com] = 0;
	break;
      case NO_RSP_DIR_REQ:		/* from the MECIF library */
	error = "Connection failed (directory request)";
	/* possibly due to physical connection failure */
	connected[com] = 0;
	break;
      case NO_RSP_RAWEN_REQ:		/* from the MECIF library ??? */
	error = "NO_RSP_RAWEN_REQ";
	/* no clue what this means */
	/* possibly due to physical connection failure */
	break;
      case NO_RSP_CON_REQ:		/* from the MECIF library */
	error = "Connection failed (connect request)";
	/* possibly due to physical connection failure */
	connected[com] = 0;
	break;
      case NO_RSP_DISCON_REQ:		/* from the MECIF library */
	error = "Connection failed (disconnect request)";
	/* possibly due to physical connection failure */
	connected[com] = 0;
	break;
      case MESSAGE_LOST:		/* from the MECIF library */
	error = "Message lost";
	/* The HP Guide seems to suggest this means that a message from the
	   PC to the monitor has been lost, not the other way around. ??? */
	break;
      case INVALID_COMMAND:		/* from the MECIF library ??? */
	error = "Invalid command";
	/* Presumably means the monitor couldn't interpret something it
	   received over the serial line. No comment in the HP Guide. ??? */
	break;
      case RS232_RESTART_OV:	/* from the monitor */
	error = "RS232 restart: MPB RX FIFO overflow";
	/* Either the monitor or the PC lost something ??? */
	break;
      case RS232_RESTART_OTHER:	/* from the monitor */
	error = "Serial board in monitor may be defective";
	/* See the HP Guide. */
	break;
      default:
	sprintf(buf, "Unknown error (code %u)", error_code);
	error = buf;
	break;
    }
    make_timestamp();
    if (error_show && error_code != NO_RSP_DISCON_REQ) {
	gotoxy(20, 25);
	clreol();
	textcolor(BLUE);
	cprintf("%s com%d: %s", time_string, com, error);
	textcolor(WHITE);
    }
    if (errorlogfp)
	fprintf(errorlogfp, "%s %s com%d (cid %d): %s\n",
		date_string, time_string, com, con, error);
    if (!initializing) {
	gotoxy(10, 18+com);
	cprintf("%5d", ++com_errors[com]);
    }
    error_flag = 1;
}

#define VZERO 2048

int findsig(mp)
MsgIdTyp *mp;
{
    int i;
    MsgIdTyp *tp;

    for (i = 0; i < nsig; i++) {
	tp = &fsmap[i];
	if (mp->SourceId == tp->SourceId &&
	    mp->ChannelId == tp->ChannelId &&
	    mp->ChannelNo == tp->ChannelNo &&
	    mp->SourceNo == tp->SourceNo)
	    break;
    }
    return (i);
}

static long pvt;
static DB_Annotation annot = { 0L, NOTE, 0, 0, 0, NULL };
#define put_sample(S,V)	(vbuf[((t[S]++ & (BSIZE-1))<<4) | S] = V)

void rx_lib_ws(WaveSupp *ws)
{
    int active, i, s;
    static int was_active[MAXSIG];
    static char onmsg[] = "\02on";	/* first char is length of remainder */
    static char offmsg[] = "\03off";

    if ((unsigned)(ws->status & 0xb100) == (unsigned)(0xa100))
	/* Parameter is on (bit 15 = 1), front end is plugged in
	   (bit 13 = 1), transducer is connected (bit 12 = 0),
	   and channel is on (bit 8 = 1). */
	active = 1;
    else active = 0;
    if (initializing) {
	for (i = 0; i < 20; i++) { /* 20: largest possible value for nwaves */
	    if (ws->msg_id.SourceId == tune_list_msg_id[i].SourceId &&
		ws->msg_id.SourceNo == tune_list_msg_id[i].SourceNo &&
		ws->msg_id.ChannelId == tune_list_msg_id[i].ChannelId &&
		ws->msg_id.ChannelNo == tune_list_msg_id[i].ChannelNo) {
		strncpy(tune_list_label[i], ws->label, 12);
		tune_list_active[i] = active;
		break;
	    }
	}
	for (i = 0; i < MAXSIG; i++)
	    was_active[i] = 1;
	nresp++;
    }
    else {
	gotoxy(5, 15);
	cprintf("%10lu", ++nws);
	s = findsig(&ws->msg_id);
	if (s == 0) {
	    make_timestamp();
	    gotoxy(60, 1);
	    cprintf(date_string);
	    gotoxy(60, 2);
	    cprintf(time_string);
	}
	gotoxy(16, s+15);
	cprintf("%6s:", si[s].desc);
	gotoxy(33, s+15);
	if (active)
	    cprintf(" on ");
	if (!active) {
	    textcolor(BLUE);
	    cprintf(" off");
	    textcolor(WHITE);
	    if (was_active[s]) {
		if (s) make_timestamp();
		if (errorlogfp)
		    fprintf(errorlogfp,
			    "%s %s: Signal %d (%s) off at sample %ld\n",
			    date_string, time_string, s, si[s].desc, t[s]);
		annot.time = t[s] - t0;
		annot.chan = s;
		annot.aux = offmsg;
		putann(0, &annot);
	    }
	    switch (si[s].spf) {
	      case 1:
		for (i = 0; i < 128; i++)
		    put_sample(smap[s], 0);
		break;
	      case 4:
		for (i = 0; i < 512; i++)
		    put_sample(smap[s] + (i&3), 0);
		break;
	      default:	/* shouldn't happen */
		break;
	    }
	    flush_buf();
	}
	else if (!was_active[s] && errorlogfp) {
	    make_timestamp();
	    fprintf(errorlogfp, "%s %s: Signal %d (%s) on at sample %ld\n",
		    date_string, time_string, s, si[s].desc, t[s]);
	    annot.time = t[s] - t0;
	    annot.chan = s;
	    annot.aux = onmsg;
	    putann(0, &annot);
	}
	was_active[s] = active;
    }
}

void flush_buf()
{
    int s;
    long tt;
    static long rl;

    for (s = 0, tt = pvt+2*BSIZE; s < tspf; s++)
	if (t[s] < tt) tt = t[s];
    while (pvt < tt) {
	putvec(&vbuf[(pvt++ & (BSIZE-1)) << 4]);
	if (++rl >= record_length) {
	    rl = 0L;
	    openlog();
	}
    }
}

void rx_lib_cw(CookedWave *cw)
{
    int i, s;
    static long np[MAXSIG];

    if (initializing)
	nresp++;
    else {
	gotoxy(5, 16);
	cprintf("%10lu", ++ncw);
	s = findsig(&cw->msg_id);	/* get signal number */
	gotoxy(23, 15+s);
	cprintf("%10lu", ++np[s]);
	switch (cw->num_of_samples) {
	  case 4:
	    for (i = 0; i < 4; i++)
		put_sample(smap[s], cw->samples[i] - VZERO);
	    break;
	  case 16:
	    for (i = 0; i < 16; i++)
		put_sample(smap[s]+(i&3), cw->samples[i] - VZERO);
	    break;
	  default:	/* shouldn't happen */
	    break;
	}
	flush_buf();
    }
}

int findmeas(s)
char *s;
{
    int i;

    for (i = 0; i < nmeas && strcmp(numap[i], s); i++)
	;
    return (i);
}

void rx_lib_nu(Numeric *nu)
{
    int active, i, m;

    if ((unsigned)(nu->status & 0xb100) == (unsigned)(0xa100))
	/* Parameter is on (bit 15 = 1), front end is plugged in
	   (bit 13 = 1), transducer is connected (bit 12 = 0),
	   and channel is on (bit 8 = 1). */
	active = 1;
    else active = 0;
    if (initializing) {
	for (i = 40; i < 88; i++) {
	    if (nu->msg_id.SourceId == tune_list_msg_id[i].SourceId &&
		nu->msg_id.SourceNo == tune_list_msg_id[i].SourceNo &&
		nu->msg_id.ChannelId == tune_list_msg_id[i].ChannelId &&
		nu->msg_id.ChannelNo == tune_list_msg_id[i].ChannelNo) {
		strncpy(tune_list_label[i], nu->label, 12);
		tune_list_active[i] = active;
	    }
	}
	nresp++;
    }
    else {
	gotoxy(5, 17);
	cprintf("%10lu", ++nnu);
	m = findmeas(nu->label);
	gotoxy(40, m+15);
	fprintf(nufp, "%s", nu->label);
	if (active) {
	    cprintf("%s:", nu->label);
	    for (i = 0; i < nu->num_of_values; i++) {
		cprintf("%10g", nu->numeric[i].value);
		fprintf(nufp, "\t%g", nu->numeric[i].value);
	    }
	}
	else {
	    textcolor(BLUE);
	    cprintf("%s: [inactive]", nu->label);
	    textcolor(WHITE);
	    fprintf(nufp, "[inactive, status = %x]", nu->status);
	}
	clreol();
	fprintf(nufp, "\n");
    }
}

char textbuf[80];

void rx_lib_al(Alert *al)
{
    int i, m;

    if (initializing)
	nresp++;
    else {
	static long altime;	/* time of most recent alarm */

	if (strcmp(al->unsptext, altext+8) || (t[0] - altime > 250L)) {
	    /* Alarms are raised repeatedly at 1024 ms intervals.  Only the
	       first alarm of a given series occuring in any given segment is
	       recorded in the annotation file (all are recorded in the .txt
	       file, however).  The openlog() function resets altext so that
	       an alarm condition that spans a segment boundary appears once
	       in each annotation file. */
	    sprintf(altext+1, "ALARM: %s", al->unsptext);
	    altext[0] = strlen(altext+1);
	    annot.time = t[0] - t0;  /* sample number within current segment */
	    annot.chan = 0;
	    annot.aux = altext;
	    putann(0, &annot);
	}
	altime = t[0];
	gotoxy(8, 18);
	cprintf("%7lu", ++nal);
	fprintf(nufp, "ALARM\t%s\n", al->unsptext);
    }
}

void rx_lib_alstat(AlStat *al)
{
    int i, m;

    if (initializing)
	nresp++;
    else {
	gotoxy(8, 18);
	cprintf("%7lu", ++nal);
	fprintf(nufp, "ALSTAT\t%d %d %d %d\n", al->al_severity,
		al->inop_severity, al->mon_status, al->al_suspended);
    }
}

void rx_lib_inop(Alert *al)
{
    int i, m;

    if (initializing)
	nresp++;
    else {
#if 0	/* enable this block to log inops in the annotation files */
	static char inoptext[80];

	sprintf(inoptext+1, "INOP: %s", al->unsptext);
	inoptext[0] = strlen(inoptext+1);
	annot.time = t[0] - t0;
	annot.chan = 0;
	annot.aux = inoptext;
	putann(0, &annot);
#endif
	gotoxy(8, 18);
	cprintf("%7lu", ++nal);
	fprintf(nufp, "INOP\t%s\n", al->unsptext);
    }
}

void resynch()
{
    int i, s;
    long tt;
    LibRet *status;

    if (cid[1] && tune_list[1].num_tune) {
	detune_all_req(cid[1], 0);
	for (i = 0; i < 25; i++) {
	    status = rx_mecif();
	    if (status->cmd == DETUNE_ALL_REQ) break;
	}
    }
    if (cid[2] && tune_list[2].num_tune) {
	detune_all_req(cid[2], 0);
	for (i = 0; i < 25; i++) {
	    status = rx_mecif();
	    if (status->cmd == DETUNE_ALL_REQ) break;
	}
    }

    for (s = 1, tt = t[0]; s < tspf; s++)
	if (t[s] > tt) tt = t[s];
    for (s = 0; s < tspf; s++)
	t[s] = tt;
    flush_buf();

    if (tune_lib_req(cid[porta],&tune_list[porta],CONTINUOUS) != SUCCESS)
	cleanup(6);
    do {
	status = rx_mecif();
	if (kbhit()) cleanup(7);
    } while (status->cmd != TUNE_RSP || status->ret.pret == NULL);

    if (portb && tune_list[portb].num_tune) {
	if (tune_lib_req(cid[portb], &tune_list[portb], CONTINUOUS) !=
	    SUCCESS)
	    cleanup(8);
	do {
	    status = rx_mecif();
	    if (kbhit()) cleanup(9);
	} while (status->cmd != TUNE_RSP || status->ret.pret == NULL);
    }

    if (portc && tune_list[portc].num_tune) {
	if (tune_lib_req(cid[portc], &tune_list[portc], CONTINUOUS) !=
	    SUCCESS)
	    cleanup(10);
	do {
	    status = rx_mecif();
	    if (kbhit()) cleanup(11);
	} while (status->cmd != TUNE_RSP || status->ret.pret == NULL);
    }

    if (portd && tune_list[portd].num_tune) {
	if (tune_lib_req(cid[portd], &tune_list[portd], CONTINUOUS) !=
	    SUCCESS)
	    cleanup(12);
	do {
	    status = rx_mecif();
	    if (kbhit()) cleanup(13);
	} while (status->cmd != TUNE_RSP || status->ret.pret == NULL);
    }
}