/*	SerialPort.m
	24 March 1993
	Eric Celeste / AppTech / 5 Exeter Street / Belmont, MA 02178
	efc@mit.edu

	This simple serial port object was originally created by:
		Michael Hawley
		MIT Media Laboratory
		mike@media-lab.mit.edu, mjh@next.com
	Please send us both any improvements.
	
	For example:
		port = [SerialPort new:"/dev/ttyb" baud:B4800];
		...
		[port puts:"a string"];
		character = [port getc];
		...
		[port free];
	
	I've simplified this object for distribution with the Laserdisc
	object. My contribution to its function is nil (I may even have 
	made it less elegant). Mostly I needed to rewrite it so that I
	could read the code and understand what was going on.
	
	////////////////////////////////////////// History
	930213	mh   shared this object with efc
	930324	efc  reformated the code so he could understand it

 */
#import "SerialPort.h"
#import <stdio.h>
#import <sys/time.h>
#import <dpsclient/dpsNeXT.h>

extern int open(), close(), read(), write(), ioctl(), select(), strlen();

@implementation SerialPort

////////////////////////////////////////////// Private Method
- _fdHandler:(int)f
 /* DPS handler for output from process */
 {
	#define BUFSIZE 1024
    char b[BUFSIZE];
    int n;
	
	#define min(a,b) (a<b? a : b)
	if (fdWaitInput(f,1)<=0 || (n=read(f,b,min(fdHasInput(f),BUFSIZE-1)))<=0)
		return self;
    b[n] = '\0';
    if (delegate && [delegate respondsTo:action])
	[delegate perform:action with:(void *)&b];
    return self;
 }

////////////////////////////////////////////// Private Functions
static void _fgets(char *s, int n, int fd, char *eol, int delay)
 {
	while (fdWaitInput(fd,1) <= 0 && fdHasInput(fd) <= 0) {
		/* skip this */
	}
	while (fdWaitInput(fd,delay)>0) {
		if (read(fd,s,1) != 1) {
			*s = '\0';
			return;
	    }
	    if (*s == *eol || --n <= 0) {
			*++s = '\0';
			return;
	    }
	    ++s;
	}
	*++s = '\0';
 }

static void fdHandler(int f, id self)
 {
 	[self _fdHandler:f];
 }

////////////////////////////////////////////// Allocation Methods
+ new: (char *) devicepath baud:(int)b parity:(int)p echo:(int)e raw:(int)r
 /*	Open a serial port with the given speed and modes.
	For example, a 4800 baud video disk device is hooked to Serial Port B.
	The correct call for this baud rate, any parity taken/ none given,
	and no echo by the NeXT is:
	
		p = [SerialPort new: "/dev/ttyb"
				baud: B4800
				parity: ANYP
				echo: NO
				raw: YES]; 
 */
 {
	self = [super new];
	eol = "\r";
	delay = 3;
	if ((fd = open(devicepath,O_RDWR)) > 0) {
		[self setBaud:b parity:p echo:e raw:r];
    } else {
		self = nil;
	}
	return self;
 }

+ new:(char *)devicepath baud:(int)b
 /* new: with parity:ANYP echo:NO raw:YES */
 {
    return [SerialPort new:devicepath baud:b parity:ANYP echo:NO raw:YES];
 }

- free
 {
	if (fd>0){
		if (action) DPSRemoveFD(fd);
		close(fd);
    }
	self = [super free];
	return self;
 }

////////////////////////////////////////////// Parameter Methods
- setBaud:(int)b parity:(int)p echo:(int)e raw:(int)r
 {
    struct sgttyb s;
	
    speed = b; 
	parity = p; 
	echo = e; 
	raw = r;
	
    ioctl(fd, TIOCGETP, &s);
    s.sg_ispeed = s.sg_ospeed = speed;
    s.sg_flags = 0;
	
	#define setbits(f,mask) if (f) s.sg_flags|=mask; else s.sg_flags&=~(mask)
    setbits(r,RAW|CBREAK);
    setbits(e,ECHO);
    s.sg_flags |= p;
    ioctl(fd, TIOCSETP, &s);
    ioctl(fd, TIOCFLUSH, &s);	// maybe not necessary
	
    return self;
 }

- (char *)eol
 {
 	return eol;
 }
 
- setEol:(char *)s
 {
 	eol = s;
	return self;
 }

- (int)delay
 {
 	return delay;
 }
- setDelay:(int)n
 {
 	delay = n;
	return self;
 }

- (int)fd
 {
 	return fd;
 }

////////////////////////////////////////////// Input
int fdHasInput(int fd)
 /* Number of pending input characters. */
 {
 	int x;
	
	ioctl(fd,FIONREAD,&x);
	return x;
 }

- (int)hasInput
 {
 	return fdHasInput(fd);
 }
 
int fdWaitInput(int fd, int timeout)
 /* Return >0 when 'fd' is readable (has input pending),
 	'0' if timeout, '-1' on error.  e.g., 'fdWaitInput(0,0)'
	will be true when the standard input contains something.
 */
 {
    int readfd = 1<<fd;
    struct timeval t = {(unsigned long)timeout,0};
	
    return select(fd+1, (fd_set *)&readfd, NULL, NULL, &t);
 }

- (int)waitInput:(int)seconds
 { 
 	return fdWaitInput(fd,seconds);
 }

- flush
 {
 	int x;
	
	ioctl(fd, TIOCFLUSH, &x);
	return self;
 }
 
////////////////////////////////////////////// Reading & Writing Methods
- (int)putc:(char)c
 { 
 	return write(fd,&c,1);
 }
 
- (int)write:(char *)s length:(int)n
 {
 	return write(fd,s,n);
 }
- (int)puts:(char *)s
 {
	write(fd, s, strlen(s));
    return write(fd, eol, strlen(eol));
 }

- (char)getc
 {
 	char c = '\0';
	read(fd,&c,1);
	return c;
 }
- (int)read:(char *)s length:(int)n
 {
 	return read(fd,s,n);
 }

- (char *)gets:(char *)s length:(int)n
 {
	*s = '\0';
	_fgets(s,n,fd,eol,delay);
	return s;
 }

- (char *)gets:(char *)s
 {
 	return [self gets:s length:256];
 }

- (char *)puts:(char *)s gets:(char *)response
 {
    [self puts:s];
    [self gets:response];
    while ([self hasInput]) [self gets:response];
    return response;
 }

////////////////////////////////////////////// Delegate Methods 
 /*	Delegate methods, for asynchronous input.
	For example, to have a delegate routine called when input arrives:
		[port setDelegate:self];
		[port setAction:@selector(processOutput:)];
		...
		- processOutput:(char *)s { ... }
		
	NB, unlike gets:..., fdHandler does not do fancy waiting 
	or eol-processing. This is probably alright for line-buffered 
	devices.
 */

- (void)setAction:(SEL)theAction
 {
    DPSAddFD(fd,(DPSFDProc)fdHandler,(id)self,11);
    action = theAction;
 }

- delegate
 {
 	return delegate;
 }

- setDelegate:d
 {
 	delegate = d;
	return self;
 }
 
@end
