refclock_acts.c   [plain text]


/*
 * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time
 *	Services
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && (defined(CLOCK_ACTS) || defined(CLOCK_PTBACTS))

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"
#include "ntp_control.h"

#include <stdio.h>
#include <ctype.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */

/*
 * This driver supports the US (NIST, USNO) and European (PTB, NPL,
 * etc.) modem time services, as well as Spectracom GPS and WWVB
 * receivers connected via a modem. The driver periodically dials a
 * number from a telephone list, receives the timecode data and
 * calculates the local clock correction. It is designed primarily for
 * use as backup when neither a radio clock nor connectivity to Internet
 * time servers is available.
 *
 * This driver requires a modem with a Hayes-compatible command set and
 * control over the modem data terminal ready (DTR) control line. The
 * modem setup string is hard-coded in the driver and may require
 * changes for nonstandard modems or special circumstances. For reasons
 * unrelated to this driver, the data set ready (DSR) control line
 * should not be set when this driver is first started.
 *
 * The calling program is initiated by setting fudge flag1, either
 * manually or automatically. When flag1 is set, the calling program
 * dials the first number in the phone command of the configuration
 * file. If that call fails, the calling program dials the second number
 * and so on. The number is specified by the Hayes ATDT prefix followed
 * by the number itself, including the prefix and long-distance digits
 * and delay code, if necessary. The flag1 is reset and the calling
 * program terminated if (a) a valid clock update has been determined,
 * (b) no more numbers remain in the list, (c) a device fault or timeout
 * occurs or (d) fudge flag1 is reset manually.
 *
 * The driver is transparent to each of the modem time services and
 * Spectracom radios. It selects the parsing algorithm depending on the
 * message length. There is some hazard should the message be corrupted.
 * However, the data format is checked carefully and only if all checks
 * succeed is the message accepted. Corrupted lines are discarded
 * without complaint.
 *
 * Fudge controls
 *
 * flag1	force a call in manual mode
 * flag2	enable port locking (not verified)
 * flag3	no modem; port is directly connected to device
 * flag4	not used
 *
 * time1	offset adjustment (s)
 *
 * Ordinarily, the serial port is connected to a modem; however, it can
 * be connected directly to a device or another computer for testing and
 * calibration. In this case set fudge flag3 and the driver will send a
 * single character 'T' at each poll event. In principle, fudge flag2
 * enables port locking, allowing the modem to be shared when not in use
 * by this driver. At least on Solaris with the current NTP I/O
 * routines, this results only in lots of ugly error messages.
 */
/*
 * National Institute of Science and Technology (NIST)
 *
 * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii)
 *
 * Data Format
 *
 * National Institute of Standards and Technology
 * Telephone Time Service, Generator 3B
 * Enter question mark "?" for HELP
 *                         D  L D
 *  MJD  YR MO DA H  M  S  ST S UT1 msADV        <OTM>
 * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF>
 * ...
 *
 * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is
 * the on-time markers echoed by the driver and used by NIST to measure
 * and correct for the propagation delay.
 *
 * US Naval Observatory (USNO)
 *
 * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO)
 *
 * Data Format (two lines, repeating at one-second intervals)
 *
 * jjjjj nnn hhmmss UTC<CR><LF>
 * *<CR><LF>
 *
 * jjjjj	modified Julian day number (not used)
 * nnn		day of year
 * hhmmss	second of day
 * *		on-time marker for previous timecode
 * ...
 *
 * USNO does not correct for the propagation delay. A fudge time1 of
 * about .06 s is advisable.
 *
 * European Services (PTB, NPL, etc.)
 *
 * PTB: +49 531 512038 (Germany)
 * NPL: 0906 851 6333 (UK only)
 *
 * Data format (see the documentation for phone numbers and formats.)
 *
 * 1995-01-23 20:58:51 MEZ  10402303260219950123195849740+40000500<CR><LF>
 *
 * Spectracom GPS and WWVB Receivers
 *
 * If a modem is connected to a Spectracom receiver, this driver will
 * call it up and retrieve the time in one of two formats. As this
 * driver does not send anything, the radio will have to either be
 * configured in continuous mode or be polled by another local driver.
 */
/*
 * Interface definitions
 */
#define	DEVICE		"/dev/acts%d" /* device name and unit */
#define	SPEED232	B9600	/* uart speed (9600 baud) */
#define	PRECISION	(-10)	/* precision assumed (about 1 ms) */
#define LOCKFILE	"/var/spool/locks/LCK..cua%d"
#define DESCRIPTION	"Automated Computer Time Service" /* WRU */
#define REFID		"NONE"	/* default reference ID */
#define MSGCNT		20	/* max message count */
#define SMAX		256	/* max clockstats line length */
#define	MAXPHONE	10	/* max number of phone numbers */

/*
 * Calling program modes
 */
#define MODE_AUTO	0	/* automatic mode */
#define MODE_BACKUP	1	/* backup mode */
#define MODE_MANUAL	2	/* manual mode */

/*
 * Service identifiers.
 */
#define REFACTS		"NIST"	/* NIST reference ID */
#define LENACTS		50	/* NIST format */
#define REFUSNO		"USNO"	/* USNO reference ID */
#define LENUSNO		20	/* USNO */
#define REFPTB		"PTB\0"	/* PTB/NPL reference ID */
#define LENPTB		78	/* PTB/NPL format */
#define REFWWVB		"WWVB"	/* WWVB reference ID */
#define	LENWWVB0	22	/* WWVB format 0 */
#define	LENWWVB2	24	/* WWVB format 2 */
#define LF		0x0a	/* ASCII LF */

/*
 * Modem setup strings. These may have to be changed for some modems.
 *
 * AT	command prefix
 * B1	US answer tone
 * &C0	disable carrier detect
 * &D2	hang up and return to command mode on DTR transition
 * E0	modem command echo disabled
 * l1	set modem speaker volume to low level
 * M1	speaker enabled until carrier detect
 * Q0	return result codes
 * V1	return result codes as English words
 */
#define MODEM_SETUP	"ATB1&C0&D2E0L1M1Q0V1\r" /* modem setup */
#define MODEM_HANGUP	"ATH\r"	/* modem disconnect */

/*
 * Timeouts (all in seconds)
 */
#define SETUP		3	/* setup timeout */
#define	DTR		1	/* DTR timeout */
#define ANSWER		60	/* answer timeout */
#define CONNECT		20	/* first valid message timeout */
#define TIMECODE	30	/* all valid messages timeout */

/*
 * State machine codes
 */
#define S_IDLE		0	/* wait for poll */
#define S_OK		1	/* wait for modem setup */
#define S_DTR		2	/* wait for modem DTR */
#define S_CONNECT	3	/* wait for answer*/
#define S_FIRST		4	/* wait for first valid message */
#define S_MSG		5	/* wait for all messages */
#define S_CLOSE		6	/* wait after sending disconnect */

/*
 * Unit control structure
 */
struct actsunit {
	int	unit;		/* unit number */
	int	state;		/* the first one was Delaware */
	int	timer;		/* timeout counter */
	int	retry;		/* retry index */
	int	msgcnt;		/* count of messages received */
	l_fp	tstamp;		/* on-time timestamp */
	char	*bufptr;	/* buffer pointer */
};

/*
 * Function prototypes
 */
static	int	acts_start	(int, struct peer *);
static	void	acts_shutdown	(int, struct peer *);
static	void	acts_receive	(struct recvbuf *);
static	void	acts_message	(struct peer *);
static	void	acts_timecode	(struct peer *, char *);
static	void	acts_poll	(int, struct peer *);
static	void	acts_timeout	(struct peer *);
static	void	acts_disc	(struct peer *);
static	void	acts_timer	(int, struct peer *);

/*
 * Transfer vector (conditional structure name)
 */
struct	refclock refclock_acts = {
	acts_start,		/* start up driver */
	acts_shutdown,		/* shut down driver */
	acts_poll,		/* transmit poll message */
	noentry,		/* not used */
	noentry,		/* not used */
	noentry,		/* not used */
	acts_timer		/* housekeeping timer */
};

/*
 * Initialize data for processing
 */
static int
acts_start (
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;

	/*
	 * Allocate and initialize unit structure
	 */
	up = emalloc(sizeof(struct actsunit));
	memset(up, 0, sizeof(struct actsunit));
	up->unit = unit;
	pp = peer->procptr;
	pp->unitptr = (caddr_t)up;
	pp->io.clock_recv = acts_receive;
	pp->io.srcclock = (caddr_t)peer;
	pp->io.datalen = 0;

	/*
	 * Initialize miscellaneous variables
	 */
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	memcpy((char *)&pp->refid, REFID, 4);
	peer->sstclktype = CTL_SST_TS_TELEPHONE;
	up->bufptr = pp->a_lastcode;
	return (1);
}


/*
 * acts_shutdown - shut down the clock
 */
static void
acts_shutdown (
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;

	/*
	 * Warning: do this only when a call is not in progress.
	 */
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
	free(up);
}


/*
 * acts_receive - receive data from the serial interface
 */
static void
acts_receive (
	struct recvbuf *rbufp
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	struct peer *peer;
	char	tbuf[BMAX];
	char	*tptr;

	/*
	 * Initialize pointers and read the timecode and timestamp. Note
	 * we are in raw mode and victim of whatever the terminal
	 * interface kicks up; so, we have to reassemble messages from
	 * arbitrary fragments. Capture the timecode at the beginning of
	 * the message and at the '*' and '#' on-time characters.
	 */
	peer = (struct peer *)rbufp->recv_srcclock;
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
	pp->lencode = refclock_gtraw(rbufp, tbuf, BMAX - (up->bufptr -
	    pp->a_lastcode), &pp->lastrec);
	for (tptr = tbuf; *tptr != '\0'; tptr++) {
		if (*tptr == LF) {
			if (up->bufptr == pp->a_lastcode) {
				up->tstamp = pp->lastrec;
				continue;

			} else {
				*up->bufptr = '\0';
				acts_message(peer);
				up->bufptr = pp->a_lastcode;
			}
		} else if (!iscntrl(*tptr)) {
			*up->bufptr++ = *tptr;
			if (*tptr == '*' || *tptr == '#') {
				up->tstamp = pp->lastrec;
				write(pp->io.fd, tptr, 1);
			}
		}
	}
}


/*
 * acts_message - process message
 */
void
acts_message(
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	int	dtr = TIOCM_DTR;
	char	tbuf[SMAX];
#ifdef DEBUG
	u_int	modem;
#endif

	/*
	 * What to do depends on the state and the first token in the
	 * message.	 */
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
#ifdef DEBUG
	ioctl(pp->io.fd, TIOCMGET, (char *)&modem);
	snprintf(tbuf, sizeof(tbuf), "acts: %04x (%d %d) %d %s", modem,
	    up->state, up->timer, strlen(pp->a_lastcode),
	    pp->a_lastcode);
	if (debug)
		printf("%s\n", tbuf);
#endif

	/*
	 * Extract the first token in the line. A NO token sends the
	 * message to the clockstats.
	 */
	strncpy(tbuf, pp->a_lastcode, SMAX);
	strtok(tbuf, " ");
	if (strcmp(tbuf, "NO") == 0) {
		report_event(PEVNT_CLOCK, peer, pp->a_lastcode);
		return;
	}
	switch(up->state) {

	/*
	 * We are waiting for the OK response to the modem setup
	 * command. When this happens, raise DTR and dial the number
	 * followed by \r.
	 */
	case S_OK:
		if (strcmp(tbuf, "OK") != 0) {
			msyslog(LOG_ERR, "acts: setup error %s",
			    pp->a_lastcode);
			acts_disc(peer);
			return;
		}
		ioctl(pp->io.fd, TIOCMBIS, (char *)&dtr);
		up->state = S_DTR;
		up->timer = DTR;
		return;

	/*
	 * We are waiting for the call to be answered. All we care about
	 * here is token CONNECT. Send the message to the clockstats.
	 */
	case S_CONNECT:
		report_event(PEVNT_CLOCK, peer, pp->a_lastcode);
		if (strcmp(tbuf, "CONNECT") != 0) {
			acts_disc(peer);
			return;
		}
		up->state = S_FIRST;
		up->timer = CONNECT;
		return;

	/*
	 * We are waiting for a timecode. Pass it to the parser.
	 */
	case S_FIRST:
	case S_MSG:
		acts_timecode(peer, pp->a_lastcode);
		break;
	}
}

/*
 * acts_timecode - identify the service and parse the timecode message
 */
void
acts_timecode(
	struct peer *peer,	/* peer structure pointer */
	char	*str		/* timecode string */
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	int	day;		/* day of the month */
	int	month;		/* month of the year */
	u_long	mjd;		/* Modified Julian Day */
	double	dut1;		/* DUT adjustment */

	u_int	dst;		/* ACTS daylight/standard time */
	u_int	leap;		/* ACTS leap indicator */
	double	msADV;		/* ACTS transmit advance (ms) */
	char	utc[10];	/* ACTS timescale */
	char	flag;		/* ACTS on-time character (* or #) */

	char	synchar;	/* WWVB synchronized indicator */
	char	qualchar;	/* WWVB quality indicator */
	char	leapchar;	/* WWVB leap indicator */
	char	dstchar;	/* WWVB daylight/savings indicator */
	int	tz;		/* WWVB timezone */

	u_int	leapmonth;	/* PTB/NPL month of leap */
	char	leapdir;	/* PTB/NPL leap direction */

	/*
	 * The parser selects the modem format based on the message
	 * length. Since the data are checked carefully, occasional
	 * errors due noise are forgivable.
	 */
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
	pp->nsec = 0;
	switch(strlen(str)) {

	/*
	 * For USNO format on-time character '*', which is on a line by
	 * itself. Be sure a timecode has been received.
	 */
	case 1:
		if (*str == '*' && up->msgcnt > 0) 
			break;

		return;
	
	/*
	 * ACTS format: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa
	 * UTC(NIST) *"
	 */
	case LENACTS:
		if (sscanf(str,
		    "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c",
		    &mjd, &pp->year, &month, &day, &pp->hour,
		    &pp->minute, &pp->second, &dst, &leap, &dut1,
		    &msADV, utc, &flag) != 13) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}

		/*
		 * Wait until ACTS has calculated the roundtrip delay.
		 * We don't need to do anything, as ACTS adjusts the
		 * on-time epoch.
		 */
		if (flag != '#')
			return;

		pp->day = ymd2yd(pp->year, month, day);
		pp->leap = LEAP_NOWARNING;
		if (leap == 1)
	    		pp->leap = LEAP_ADDSECOND;
		else if (pp->leap == 2)
	    		pp->leap = LEAP_DELSECOND;
		memcpy(&pp->refid, REFACTS, 4);
		if (up->msgcnt == 0)
			record_clock_stats(&peer->srcadr, str);
		up->msgcnt++;
		break;

	/*
	 * USNO format: "jjjjj nnn hhmmss UTC"
	 */
	case LENUSNO:
		if (sscanf(str, "%5ld %3d %2d%2d%2d %3s",
		    &mjd, &pp->day, &pp->hour, &pp->minute,
		    &pp->second, utc) != 6) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}

		/*
		 * Wait for the on-time character, which follows in a
		 * separate message. There is no provision for leap
		 * warning.
		 */
		pp->leap = LEAP_NOWARNING;
		memcpy(&pp->refid, REFUSNO, 4);
		if (up->msgcnt == 0)
			record_clock_stats(&peer->srcadr, str);
		up->msgcnt++;
		return;

	/*
	 * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ" 
	 */
	case LENPTB:
		if (sscanf(str,
		    "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c",
		    &pp->second, &pp->year, &month, &day, &pp->hour,
		    &pp->minute, &mjd, &dut1, &leapdir, &leapmonth,
		    &msADV, &flag) != 12) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		pp->leap = LEAP_NOWARNING;
		if (leapmonth == month) {
			if (leapdir == '+')
		    		pp->leap = LEAP_ADDSECOND;
			else if (leapdir == '-')
		    		pp->leap = LEAP_DELSECOND;
		}
		pp->day = ymd2yd(pp->year, month, day);
		memcpy(&pp->refid, REFPTB, 4);
		if (up->msgcnt == 0)
			record_clock_stats(&peer->srcadr, str);
		up->msgcnt++;
		break;


	/*
	 * WWVB format 0: "I  ddd hh:mm:ss DTZ=nn"
	 */
	case LENWWVB0:
		if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d",
		    &synchar, &pp->day, &pp->hour, &pp->minute,
		    &pp->second, &dstchar, &tz) != 7) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		pp->leap = LEAP_NOWARNING;
		if (synchar != ' ')
			pp->leap = LEAP_NOTINSYNC;
		memcpy(&pp->refid, REFWWVB, 4);
		if (up->msgcnt == 0)
			record_clock_stats(&peer->srcadr, str);
		up->msgcnt++;
		break;

	/*
	 * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD"
	 */
	case LENWWVB2:
		if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c",
		    &synchar, &qualchar, &pp->year, &pp->day,
		    &pp->hour, &pp->minute, &pp->second, &pp->nsec,
		    &dstchar, &leapchar, &dstchar) != 11) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		pp->nsec *= 1000000;
		pp->leap = LEAP_NOWARNING;
		if (synchar != ' ')
			pp->leap = LEAP_NOTINSYNC;
		else if (leapchar == 'L')
			pp->leap = LEAP_ADDSECOND;
		memcpy(&pp->refid, REFWWVB, 4);
		if (up->msgcnt == 0)
			record_clock_stats(&peer->srcadr, str);
		up->msgcnt++;
		break;

	/*
	 * None of the above. Just forget about it and wait for the next
	 * message or timeout.
	 */
	default:
		return;
	}

	/*
	 * We have a valid timecode. The fudge time1 value is added to
	 * each sample by the main line routines. Note that in current
	 * telephone networks the propatation time can be different for
	 * each call and can reach 200 ms for some calls.
	 */
	peer->refid = pp->refid;
	pp->lastrec = up->tstamp;
	if (!refclock_process(pp)) {
		refclock_report(peer, CEVNT_BADTIME);
		return;
	}
	pp->lastref = pp->lastrec;
	if (up->state != S_MSG) {
		up->state = S_MSG;
		up->timer = TIMECODE;
	}
}


/*
 * acts_poll - called by the transmit routine
 */
static void
acts_poll (
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;

	/*
	 * This routine is called at every system poll. All it does is
	 * set flag1 under certain conditions. The real work is done by
	 * the timeout routine and state machine.
	 */
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
	switch (peer->ttl) {

	/*
	 * In manual mode the calling program is activated by the ntpdc
	 * program using the enable flag (fudge flag1), either manually
	 * or by a cron job.
	 */
	case MODE_MANUAL:
		/* fall through */
		break;

	/*
	 * In automatic mode the calling program runs continuously at
	 * intervals determined by the poll event or specified timeout.
	 */
	case MODE_AUTO:
		pp->sloppyclockflag |= CLK_FLAG1;
		break;

	/*
	 * In backup mode the calling program runs continuously as long
	 * as either no peers are available or this peer is selected.
	 */
	case MODE_BACKUP:
		if (sys_peer == NULL || sys_peer == peer)
			pp->sloppyclockflag |= CLK_FLAG1;
		break;
	}
}


/*
 * acts_timer - called at one-second intervals
 */
static void
acts_timer(
	int	unit,
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;

	/*
	 * This routine implments a timeout which runs for a programmed
	 * interval. The counter is initialized by the state machine and
	 * counts down to zero. Upon reaching zero, the state machine is
	 * called. If flag1 is set while in S_IDLE state, force a
	 * timeout.
	 */
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
	if (pp->sloppyclockflag & CLK_FLAG1 && up->state == S_IDLE) {
		acts_timeout(peer);
		return;
	}
	if (up->timer == 0)
		return;

	up->timer--;
	if (up->timer == 0)
		acts_timeout(peer);
}


/*
 * acts_timeout - called on timeout
 */
static void
acts_timeout(
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	int	fd;
	char	device[20];
	char	lockfile[128], pidbuf[8];
	char	tbuf[SMAX];

	/*
	 * The state machine is driven by messages from the modem, when
	 * first stated and at timeout.
	 */
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
	pp->sloppyclockflag &= ~CLK_FLAG1;
	if (sys_phone[up->retry] == NULL && !(pp->sloppyclockflag &
	    CLK_FLAG3)) {
		msyslog(LOG_ERR, "acts: no phones");
		return;
	}
	switch(up->state) {

	/*
	 * System poll event. Lock the modem port and open the device.
	 */
	case S_IDLE:

		/*
		 * Lock the modem port. If busy, retry later. Note: if
		 * something fails between here and the close, the lock
		 * file may not be removed.
		 */
		if (pp->sloppyclockflag & CLK_FLAG2) {
			snprintf(lockfile, sizeof(lockfile), LOCKFILE,
			    up->unit);
			fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL,
			    0644);
			if (fd < 0) {
				msyslog(LOG_ERR, "acts: port busy");
				return;
			}
			snprintf(pidbuf, sizeof(pidbuf), "%d\n",
			    (u_int)getpid());
			write(fd, pidbuf, strlen(pidbuf));
			close(fd);
		}

		/*
		 * Open the device in raw mode and link the I/O.
		 */
		if (!pp->io.fd) {
			snprintf(device, sizeof(device), DEVICE,
			    up->unit);
			fd = refclock_open(device, SPEED232,
			    LDISC_ACTS | LDISC_RAW | LDISC_REMOTE);
			if (fd == 0) {
				msyslog(LOG_ERR,
				    "acts: open fails");
				return;
			}
			pp->io.fd = fd;
			if (!io_addclock(&pp->io)) {
				msyslog(LOG_ERR,
				    "acts: addclock fails");
				close(fd);
				pp->io.fd = 0;
				return;
			}
		}

		/*
		 * If the port is directly connected to the device, skip
		 * the modem business and send 'T' for Spectrabum.
		 */
		if (pp->sloppyclockflag & CLK_FLAG3) {
			if (write(pp->io.fd, "T", 1) < 0) {
				msyslog(LOG_ERR, "acts: write %m");
				return;
			}
			up->state = S_FIRST;
			up->timer = CONNECT;
			return;
		}

		/*
		 * Initialize the modem. This works with Hayes commands.
		 */
#ifdef DEBUG
		if (debug)
			printf("acts: setup %s\n", MODEM_SETUP);
#endif
		if (write(pp->io.fd, MODEM_SETUP, strlen(MODEM_SETUP)) <
		    0) {
			msyslog(LOG_ERR, "acts: write %m");
			return;
		}
		up->state = S_OK;
		up->timer = SETUP;
		return;

	/*
	 * In OK state the modem did not respond to setup.
	 */
	case S_OK:
		msyslog(LOG_ERR, "acts: no modem");
		break;

	/*
	 * In DTR state we are waiting for the modem to settle down
	 * before hammering it with a dial command.
	 */
	case S_DTR:
		snprintf(tbuf, sizeof(tbuf), "DIAL #%d %s", up->retry,
		    sys_phone[up->retry]);
		report_event(PEVNT_CLOCK, peer, tbuf);
#ifdef DEBUG
		if (debug)
			printf("%s\n", tbuf);
#endif
		write(pp->io.fd, sys_phone[up->retry],
		    strlen(sys_phone[up->retry]));
		write(pp->io.fd, "\r", 1);
		up->state = S_CONNECT;
		up->timer = ANSWER;
		return;

	/*
	 * In CONNECT state the call did not complete.
	 */
	case S_CONNECT:
		msyslog(LOG_ERR, "acts: no answer");
		break;

	/*
	 * In FIRST state no messages were received.
	 */
	case S_FIRST:
		msyslog(LOG_ERR, "acts: no messages");
		break;

	/*
	 * In CLOSE state hangup is complete. Close the doors and
	 * windows and get some air.
	 */
	case S_CLOSE:

		/*
		 * Close the device and unlock a shared modem.
		 */
		if (pp->io.fd) {
			io_closeclock(&pp->io);
			close(pp->io.fd);
			if (pp->sloppyclockflag & CLK_FLAG2) {
				snprintf(lockfile, sizeof(lockfile),
				    LOCKFILE, up->unit);
				unlink(lockfile);
			}
			pp->io.fd = 0;
		}

		/*
		 * If messages were received, fold the tent and wait for
		 * the next poll. If no messages and there are more
		 * numbers to dial, retry after a short wait.
		 */
		up->bufptr = pp->a_lastcode;
		up->timer = 0;
		up->state = S_IDLE;
		if ( up->msgcnt == 0) {
			up->retry++;
			if (sys_phone[up->retry] == NULL)
				up->retry = 0;
			else
				up->timer = SETUP;
		} else {
			up->retry = 0;
		}
		up->msgcnt = 0;
		return;
	}
	acts_disc(peer);
}


/*
 * acts_disc - disconnect the call and clean the place up.
 */
static void
acts_disc (
	struct peer *peer
	)
{
	struct actsunit *up;
	struct refclockproc *pp;
	int	dtr = TIOCM_DTR;

	/*
	 * We get here if the call terminated successfully or if an
	 * error occured. If the median filter has something in it,
	 * feed the data to the clock filter. If a modem port, drop DTR
	 * to force command mode and send modem hangup.
	 */
	pp = peer->procptr;
	up = (struct actsunit *)pp->unitptr;
	if (up->msgcnt > 0)
		refclock_receive(peer);
	if (!(pp->sloppyclockflag & CLK_FLAG3)) {
		ioctl(pp->io.fd, TIOCMBIC, (char *)&dtr);
		write(pp->io.fd, MODEM_HANGUP, strlen(MODEM_HANGUP));
	}
	up->timer = SETUP;
	up->state = S_CLOSE;
}
#else
int refclock_acts_bs;
#endif /* REFCLOCK */