Commits

Jody McAdams  committed 71be7cc

Added an updated version of ArduinoSerial for Xcode from http://code.google.com/p/xcode-arduino-serial-communication/

  • Participants
  • Parent commits 12bb51a

Comments (0)

Files changed (64)

File ArduinoSerialXcode/ArduinoSerial/AMSDKCompatibility.h

+//
+//  AMSDKCompatibility.h
+//
+//  Created by Nick Zitzmann on 2007-10-22.
+//
+
+// AMSerialPort uses types that were introduced with the 10.5 SDK.
+// This allows older SDKs to be used.
+
+#import <Foundation/Foundation.h>
+
+#ifndef NSINTEGER_DEFINED
+	#ifdef NS_BUILD_32_LIKE_64
+		typedef long NSInteger;
+		typedef unsigned long NSUInteger;
+	#else
+		typedef int NSInteger;
+		typedef unsigned int NSUInteger;
+	#endif
+	#define NSIntegerMax    LONG_MAX
+	#define NSIntegerMin    LONG_MIN
+	#define NSUIntegerMax   ULONG_MAX
+	#define NSINTEGER_DEFINED 1
+#endif
+
+#ifndef CGFLOAT_DEFINED
+	typedef float CGFloat;
+	#define CGFLOAT_MIN FLT_MIN
+	#define CGFLOAT_MAX FLT_MAX
+	#define CGFLOAT_IS_DOUBLE 0
+	#define CGFLOAT_DEFINED 1
+#endif
+
+#ifndef _SUSECONDS_T
+#define _SUSECONDS_T
+typedef int suseconds_t;
+#endif

File ArduinoSerialXcode/ArduinoSerial/AMSerialErrors.h

+/*
+ *  AMSerialErrors.h
+ *
+ *  Created by Andreas on 27.07.06.
+ *  Copyright 2006 Andreas Mayer. All rights reserved.
+ *
+ */
+
+
+enum {
+	kAMSerialErrorNone = 0,
+	kAMSerialErrorFatal = 99,
+	
+	// reading only
+	kAMSerialErrorTimeout = 100,
+	kAMSerialErrorInternalBufferFull = 101,
+	
+	// writing only
+	kAMSerialErrorNoDataToWrite = 200,
+	kAMSerialErrorOnlySomeDataWritten = 201,
+};
+
+enum {
+	// reading only
+	kAMSerialEndOfStream = 0,
+	kAMSerialStopCharReached = 1,
+	kAMSerialStopLengthReached = 2,
+	kAMSerialStopLengthExceeded = 3,
+};

File ArduinoSerialXcode/ArduinoSerial/AMSerialPort.h

+//
+//  AMSerialPort.h
+//
+//  Created by Andreas on 2002-04-24.
+//  Copyright (c) 2001 Andreas Mayer. All rights reserved.
+//
+//
+//
+//  2002-09-18 Andreas Mayer
+//  - added available & owner
+//  2002-10-17 Andreas Mayer
+//	- countWriteInBackgroundThreads and countWriteInBackgroundThreadsLock added
+//  2002-10-25 Andreas Mayer
+//	- more additional instance variables for reading and writing in background
+//  2004-02-10 Andreas Mayer
+//    - added delegate for background reading/writing
+//  2005-04-04 Andreas Mayer
+//	- added setDTR and clearDTR
+//  2006-07-28 Andreas Mayer
+//	- added -canonicalMode, -endOfLineCharacter and friends
+//	  (code contributed by Randy Bradley)
+//	- cleaned up accessor methods; moved deprecated methods to "Deprecated" category
+//	- -setSpeed: does support arbitrary values on 10.4 and later; returns YES on success, NO otherwiese
+//  2006-08-16 Andreas Mayer
+//	- cleaned up the code and removed some (presumably) unnecessary locks
+//  2007-10-26 Sean McBride
+//  - made code 64 bit and garbage collection clean
+//	2009-3-20 Pat O'Keefe
+//	- fixed setSpeed method
+
+
+/*
+ * Standard speeds defined in termios.h
+ *
+#define B0	0
+#define B50	50
+#define B75	75
+#define B110	110
+#define B134	134
+#define B150	150
+#define B200	200
+#define B300	300
+#define B600	600
+#define B1200	1200
+#define	B1800	1800
+#define B2400	2400
+#define B4800	4800
+#define B7200	7200
+#define B9600	9600
+#define B14400	14400
+#define B19200	19200
+#define B28800	28800
+#define B38400	38400
+#define B57600	57600
+#define B76800	76800
+#define B115200	115200
+#define B230400	230400
+ */
+
+#import "AMSDKCompatibility.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <paths.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <sysexits.h>
+#include <sys/param.h>
+
+#import <Foundation/Foundation.h>
+
+#define	AMSerialOptionServiceName @"AMSerialOptionServiceName"
+#define	AMSerialOptionSpeed @"AMSerialOptionSpeed"
+#define	AMSerialOptionDataBits @"AMSerialOptionDataBits"
+#define	AMSerialOptionParity @"AMSerialOptionParity"
+#define	AMSerialOptionStopBits @"AMSerialOptionStopBits"
+#define	AMSerialOptionInputFlowControl @"AMSerialOptionInputFlowControl"
+#define	AMSerialOptionOutputFlowControl @"AMSerialOptionOutputFlowControl"
+#define	AMSerialOptionEcho @"AMSerialOptionEcho"
+#define	AMSerialOptionCanonicalMode @"AMSerialOptionCanonicalMode"
+
+// By default, debug code is preprocessed out.  If you would like to compile with debug code enabled,
+// "#define AMSerialDebug" before including any AMSerialPort headers, as in your prefix header
+
+typedef enum {	
+	kAMSerialParityNone = 0,
+	kAMSerialParityOdd = 1,
+	kAMSerialParityEven = 2
+} AMSerialParity;
+
+typedef enum {	
+	kAMSerialStopBitsOne = 1,
+	kAMSerialStopBitsTwo = 2
+} AMSerialStopBits;
+
+// Private constant
+#define AMSER_MAXBUFSIZE  512UL//4096UL
+
+extern NSString *const AMSerialErrorDomain;
+
+@interface NSObject (AMSerialDelegate)
+- (void)serialPortReadData:(NSDictionary *)dataDictionary;
+- (void)serialPortWriteProgress:(NSDictionary *)dataDictionary;
+@end
+
+@interface AMSerialPort : NSObject
+{
+@private
+	NSString *bsdPath;
+	NSString *serviceName;
+	NSString *serviceType;
+	int fileDescriptor;
+	struct termios * __strong options;
+	struct termios * __strong originalOptions;
+	NSMutableDictionary *optionsDictionary;
+	NSFileHandle *fileHandle;
+	BOOL gotError;
+	int	lastError;
+	id owner;
+	// used by AMSerialPortAdditions only:
+	char * __strong buffer;
+	id am_readTarget;
+	SEL am_readSelector;
+	NSTimeInterval readTimeout; // for public blocking read methods and doRead
+	fd_set * __strong readfds;
+	id delegate;
+	BOOL delegateHandlesReadInBackground;
+	BOOL delegateHandlesWriteInBackground;
+	
+	NSLock *writeLock;
+	BOOL stopWriteInBackground;
+	int countWriteInBackgroundThreads;
+	NSLock *readLock;
+	BOOL stopReadInBackground;
+	int countReadInBackgroundThreads;
+	NSLock *closeLock;
+}
+
+- (id)init:(NSString *)path withName:(NSString *)name type:(NSString *)serialType;
+// initializes port
+// path is a bsdPath
+// name is an IOKit service name
+// type is an IOKit service type
+
+- (NSString *)bsdPath;
+// bsdPath (e.g. '/dev/cu.modem')
+
+- (NSString *)name;
+// IOKit service name (e.g. 'modem')
+
+- (NSString *)type;
+// IOKit service type (e.g. kIOSerialBSDRS232Type)
+
+- (NSDictionary *)properties;
+// IORegistry entry properties - see IORegistryEntryCreateCFProperties()
+
+
+- (BOOL)isOpen;
+// YES if port is open
+
+- (AMSerialPort *)obtainBy:(id)sender;
+// get this port exclusively; NULL if it's not free
+
+- (void)free;
+// give it back (and close the port if still open)
+
+- (BOOL)available;
+// check if port is free and can be obtained
+
+- (id)owner;
+// who obtained the port?
+
+
+- (NSFileHandle *)open;
+// opens port for read and write operations
+// to actually read or write data use the methods provided by NSFileHandle
+// (alternatively you may use those from AMSerialPortAdditions)
+
+- (void)close;
+// close port - no more read or write operations allowed
+
+- (BOOL)drainInput;
+- (BOOL)flushInput:(BOOL)fIn Output:(BOOL)fOut;	// (fIn or fOut) must be YES
+- (BOOL)sendBreak;
+
+- (BOOL)setDTR;
+// set DTR - not yet tested!
+
+- (BOOL)clearDTR;
+// clear DTR - not yet tested!
+
+// read and write serial port settings through a dictionary
+
+- (NSDictionary *)options;
+// will open the port to get options if neccessary
+
+- (void)setOptions:(NSDictionary *)options;
+// AMSerialOptionServiceName HAS to match! You may NOT switch ports using this
+// method.
+
+// reading and setting parameters is only useful if the serial port is already open
+- (long)speed;
+- (BOOL)setSpeed:(long)speed;
+
+- (unsigned long)dataBits;
+- (void)setDataBits:(unsigned long)bits;	// 5 to 8 (5 may not work)
+
+- (AMSerialParity)parity;
+- (void)setParity:(AMSerialParity)newParity;
+
+- (AMSerialStopBits)stopBits;
+- (void)setStopBits:(AMSerialStopBits)numBits;
+
+- (BOOL)echoEnabled;
+- (void)setEchoEnabled:(BOOL)echo;
+
+- (BOOL)RTSInputFlowControl;
+- (void)setRTSInputFlowControl:(BOOL)rts;
+
+- (BOOL)DTRInputFlowControl;
+- (void)setDTRInputFlowControl:(BOOL)dtr;
+
+- (BOOL)CTSOutputFlowControl;
+- (void)setCTSOutputFlowControl:(BOOL)cts;
+
+- (BOOL)DSROutputFlowControl;
+- (void)setDSROutputFlowControl:(BOOL)dsr;
+
+- (BOOL)CAROutputFlowControl;
+- (void)setCAROutputFlowControl:(BOOL)car;
+
+- (BOOL)hangupOnClose;
+- (void)setHangupOnClose:(BOOL)hangup;
+
+- (BOOL)localMode;
+- (void)setLocalMode:(BOOL)local;	// YES = ignore modem status lines
+
+- (BOOL)canonicalMode;
+- (void)setCanonicalMode:(BOOL)flag;
+
+- (char)endOfLineCharacter;
+- (void)setEndOfLineCharacter:(char)eol;
+
+- (void)clearError;			// call this before changing any settings
+- (BOOL)commitChanges;	// call this after using any of the above set... functions
+- (int)errorCode;				// if -commitChanges returns NO, look here for further info
+
+// setting the delegate (for background reading/writing)
+
+- (id)delegate;
+- (void)setDelegate:(id)newDelegate;
+
+// time out for blocking reads in seconds
+- (NSTimeInterval)readTimeout;
+- (void)setReadTimeout:(NSTimeInterval)aReadTimeout;
+
+- (void)readTimeoutAsTimeval:(struct timeval*)timeout;
+
+
+@end

File ArduinoSerialXcode/ArduinoSerial/AMSerialPort.m

+//
+//  AMSerialPort.m
+//
+//  Created by Andreas on 2002-04-24.
+//  Copyright (c) 2001 Andreas Mayer. All rights reserved.
+//
+//  2002-09-18 Andreas Mayer
+//  - added available & owner
+//  2002-10-10 Andreas Mayer
+//	- some log messages changed
+//  2002-10-25 Andreas Mayer
+//	- additional locks and other changes for reading and writing in background
+//  2003-11-26 James Watson
+//	- in dealloc [self close] reordered to execute before releasing closeLock
+//  2007-05-22 Nick Zitzmann
+//  - added -hash and -isEqual: methods
+//  2007-07-18 Sean McBride
+//  - behaviour change: -open and -close must now always be matched, -dealloc checks this
+//  - added -debugDescription so gdb's 'po' command gives something useful
+//  2007-07-25 Andreas Mayer
+// - replaced -debugDescription by -description; works for both, gdb's 'po' and NSLog()
+//  2007-10-26 Sean McBride
+//  - made code 64 bit and garbage collection clean
+//	2009-3-20 Pat O'Keefe
+//	- fixed setSpeed method
+
+
+#import "AMSDKCompatibility.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <paths.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <sysexits.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#import "AMSerialPort.h"
+#import "AMSerialErrors.h"
+
+#import <IOKit/serial/IOSerialKeys.h>
+#if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4)
+	#import <IOKit/serial/ioss.h>
+#endif
+
+NSString *const AMSerialErrorDomain = @"de.harmless.AMSerial.ErrorDomain";
+
+
+@implementation AMSerialPort
+
+- (id)init:(NSString *)path withName:(NSString *)name type:(NSString *)type
+	// path is a bsdPath
+	// name is an IOKit service name
+{
+	if ((self = [super init])) {
+		bsdPath = [path copy];
+		serviceName = [name copy];
+		serviceType = [type copy];
+		optionsDictionary = [[NSMutableDictionary dictionaryWithCapacity:8] retain];
+#ifndef __OBJC_GC__
+		options = (struct termios*)malloc(sizeof(*options));
+		originalOptions = (struct termios*)malloc(sizeof(*originalOptions));
+		buffer = (char*)malloc(AMSER_MAXBUFSIZE);
+		readfds = (fd_set*)malloc(sizeof(*readfds));
+#else
+		options = (struct termios*)NSAllocateCollectable(sizeof(*options), 0);
+		originalOptions = (struct termios*)NSAllocateCollectable(sizeof(*originalOptions), 0);
+		buffer = (char*)NSAllocateCollectable(AMSER_MAXBUFSIZE, 0);
+		readfds = (fd_set*)NSAllocateCollectable(sizeof(*readfds), 0);
+#endif
+		fileDescriptor = -1;
+		
+		writeLock = [[NSLock alloc] init];
+		readLock = [[NSLock alloc] init];
+		closeLock = [[NSLock alloc] init];
+		
+		// By default blocking read attempts will timeout after 1 second
+		[self setReadTimeout:1.0];
+	}
+	return self;
+}
+
+#ifndef __OBJC_GC__
+
+- (void)dealloc
+{
+#ifdef AMSerialDebug
+	if (fileDescriptor != -1)
+		NSLog(@"It is a programmer error to have not called -close on an AMSerialPort you have opened");
+#endif
+	
+	[readLock release];
+	[writeLock release];
+	[closeLock release];
+	[am_readTarget release];
+	
+	free(readfds);
+	free(buffer);
+	free(originalOptions);
+	free(options);
+	[optionsDictionary release];
+	[serviceName release];
+	[serviceType release];
+	[bsdPath release];
+	[super dealloc];
+}
+
+#else
+
+- (void)finalize
+{
+#ifdef AMSerialDebug
+	if (fileDescriptor != -1)
+		NSLog(@"It is a programmer error to have not called -close on an AMSerialPort you have opened");
+#endif
+	assert (fileDescriptor == -1);
+
+	[super finalize];
+}
+
+#endif
+
+// So NSLog and gdb's 'po' command give something useful
+- (NSString *)description
+{
+	NSString *result= [NSString stringWithFormat:@"<%@: %x = name: %@, path: %@, type: %@, fileHandle: %@, fileDescriptor: %d>", NSStringFromClass([self class]), (long unsigned)self, serviceName, bsdPath, serviceType, fileHandle, fileDescriptor];
+	return result;
+}
+
+- (NSUInteger)hash
+{
+	return [[self bsdPath] hash];
+}
+
+- (BOOL)isEqual:(id)otherObject
+{
+	if ([otherObject isKindOfClass:[AMSerialPort class]])
+		return [[self bsdPath] isEqualToString:[otherObject bsdPath]];
+	return NO;
+}
+
+
+- (id)delegate
+{
+	return delegate;
+}
+
+- (void)setDelegate:(id)newDelegate
+{
+	id old = nil;
+	
+	if (newDelegate != delegate) {
+		old = delegate;
+		delegate = [newDelegate retain];
+		[old release];
+		delegateHandlesReadInBackground = [delegate respondsToSelector:@selector(serialPortReadData:)];
+		delegateHandlesWriteInBackground = [delegate respondsToSelector:@selector(serialPortWriteProgress:)];
+	}
+}
+
+
+- (NSString *)bsdPath
+{
+	return bsdPath;
+}
+
+- (NSString *)name
+{
+	return serviceName;
+}
+
+- (NSString *)type
+{
+	return serviceType;
+}
+
+- (NSDictionary *)properties
+{
+	NSDictionary *result = nil;
+	kern_return_t kernResult; 
+	CFMutableDictionaryRef matchingDictionary;
+	io_service_t serialService;
+	
+	matchingDictionary = IOServiceMatching(kIOSerialBSDServiceValue);
+	CFDictionarySetValue(matchingDictionary, CFSTR(kIOTTYDeviceKey), (CFStringRef)[self name]);
+	if (matchingDictionary != NULL) {
+		CFRetain(matchingDictionary);
+		// This function decrements the refcount of the dictionary passed it
+		serialService = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDictionary);
+		
+		if (serialService) {
+			NSMutableDictionary *propertiesDict;
+			kernResult = IORegistryEntryCreateCFProperties(serialService, (CFMutableDictionaryRef *)&propertiesDict, kCFAllocatorDefault, 0);
+			if (kernResult == KERN_SUCCESS) {
+				result = [propertiesDict autorelease];
+			}
+		} else {
+#ifdef AMSerialDebug
+			NSLog(@"properties: no matching service for %@", matchingDictionary);
+#endif
+		}
+		CFRelease(matchingDictionary);
+		// We have sucked this service dry of information so release it now.
+		(void)IOObjectRelease(serialService);
+	}
+	return result;
+}
+
+
+- (BOOL)isOpen
+{
+	// YES if port is open
+	return (fileDescriptor >= 0);
+}
+
+- (AMSerialPort *)obtainBy:(id)sender
+{
+	// get this port exclusively; NULL if it's not free
+	if (owner == nil) {
+		owner = sender;
+		return self;
+	} else
+		return nil;
+}
+
+- (void)free
+{
+	// give it back
+	owner = nil;
+	[self close];	// you never know ...
+}
+
+- (BOOL)available
+{
+	// check if port is free and can be obtained
+	return (owner == nil);
+}
+
+- (id)owner
+{
+	// who obtained the port?
+	return owner;
+}
+
+
+- (NSFileHandle *)open // use returned file handle to read and write
+{
+	NSFileHandle *result = nil;
+	
+	const char *path = [bsdPath fileSystemRepresentation];
+	fileDescriptor = open(path, O_RDWR | O_NOCTTY); // | O_NONBLOCK);
+
+#ifdef AMSerialDebug
+	NSLog(@"open %@ (%d)\n", bsdPath, fileDescriptor);
+#endif
+	
+	if (fileDescriptor < 0)	{
+#ifdef AMSerialDebug
+		NSLog(@"Error opening serial port %@ - %s(%d).\n", bsdPath, strerror(errno), errno);
+#endif
+	} else {
+		/*
+		 if (fcntl(fileDescriptor, F_SETFL, fcntl(fileDescriptor, F_GETFL, 0) & !O_NONBLOCK) == -1)
+		 {
+			 NSLog(@"Error clearing O_NDELAY %@ - %s(%d).\n", bsdPath, strerror(errno), errno);
+		 } // ... else
+		 */
+		// get the current options and save them for later reset
+		if (tcgetattr(fileDescriptor, originalOptions) == -1) {
+#ifdef AMSerialDebug
+			NSLog(@"Error getting tty attributes %@ - %s(%d).\n", bsdPath, strerror(errno), errno);
+#endif
+		} else {
+			// Make an exact copy of the options
+			*options = *originalOptions;
+			
+			// This object owns the fileDescriptor and must dispose it later
+			// In other words, you must balance calls to -open with -close
+			fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor];
+			result = fileHandle;
+		}
+	}
+	if (!result) { // failure
+		if (fileDescriptor >= 0) {
+			close(fileDescriptor);
+		}
+		fileDescriptor = -1;
+	}
+	return result;
+}
+
+
+- (void)close
+{
+	// Traditionally it is good to reset a serial port back to
+	// the state in which you found it.  Let's continue that tradition.
+	if (fileDescriptor >= 0) {
+		//NSLog(@"close - attempt closeLock");
+		[closeLock lock];
+		//NSLog(@"close - closeLock locked");
+		
+		// kill pending read by setting O_NONBLOCK
+		if (fcntl(fileDescriptor, F_SETFL, fcntl(fileDescriptor, F_GETFL, 0) | O_NONBLOCK) == -1) {
+#ifdef AMSerialDebug
+			NSLog(@"Error clearing O_NONBLOCK %@ - %s(%d).\n", bsdPath, strerror(errno), errno);
+#endif
+		}
+		if (tcsetattr(fileDescriptor, TCSANOW, originalOptions) == -1) {
+#ifdef AMSerialDebug
+			NSLog(@"Error resetting tty attributes - %s(%d).\n", strerror(errno), errno);
+#endif
+		}
+		
+		// Disallows further access to the communications channel
+		[fileHandle closeFile];
+
+		// Release the fileHandle
+		[fileHandle release];
+		fileHandle = nil;
+		
+#ifdef AMSerialDebug
+		NSLog(@"close (%d)\n", fileDescriptor);
+#endif
+		// Close the fileDescriptor, that is our responsibility since the fileHandle does not own it
+		close(fileDescriptor);
+		fileDescriptor = -1;
+		
+		[closeLock unlock];
+		//NSLog(@"close - closeLock unlocked");
+	}
+}
+
+- (BOOL)drainInput
+{
+	BOOL result = (tcdrain(fileDescriptor) != -1);
+	return result;
+}
+
+- (BOOL)flushInput:(BOOL)fIn Output:(BOOL)fOut	// (fIn or fOut) must be YES
+{
+	int mode = 0;
+	if (fIn == YES)
+		mode = TCIFLUSH;
+	if (fOut == YES)
+		mode = TCOFLUSH;
+	if (fIn && fOut)
+		mode = TCIOFLUSH;
+	
+	BOOL result = (tcflush(fileDescriptor, mode) != -1);
+	return result;
+}
+
+- (BOOL)sendBreak
+{
+	BOOL result = (tcsendbreak(fileDescriptor, 0) != -1);
+	return result;
+}
+
+- (BOOL)setDTR
+{
+	BOOL result = (ioctl(fileDescriptor, TIOCSDTR) != -1);
+	return result;
+}
+
+- (BOOL)clearDTR
+{
+	BOOL result = (ioctl(fileDescriptor, TIOCCDTR) != -1);
+	return result;
+}
+
+
+// read and write serial port settings through a dictionary
+
+- (void)buildOptionsDictionary
+{
+	[optionsDictionary removeAllObjects];
+	[optionsDictionary setObject:[self name] forKey:AMSerialOptionServiceName];
+	[optionsDictionary setObject:[NSString stringWithFormat:@"%d", [self speed]] forKey:AMSerialOptionSpeed];
+	[optionsDictionary setObject:[NSString stringWithFormat:@"%ul", [self dataBits]] forKey:AMSerialOptionDataBits];
+	switch ([self parity]) {
+		case kAMSerialParityOdd: {
+			[optionsDictionary setObject:@"Odd" forKey:AMSerialOptionParity];
+			break;
+		}
+		case kAMSerialParityEven: {
+			[optionsDictionary setObject:@"Even" forKey:AMSerialOptionParity];
+			break;
+		}
+		default:;
+	}
+	
+	[optionsDictionary setObject:[NSString stringWithFormat:@"%d", [self stopBits]] forKey:AMSerialOptionStopBits];
+	if ([self RTSInputFlowControl])
+		[optionsDictionary setObject:@"RTS" forKey:AMSerialOptionInputFlowControl];
+	if ([self DTRInputFlowControl])
+		[optionsDictionary setObject:@"DTR" forKey:AMSerialOptionInputFlowControl];
+	
+	if ([self CTSOutputFlowControl])
+		[optionsDictionary setObject:@"CTS" forKey:AMSerialOptionOutputFlowControl];
+	if ([self DSROutputFlowControl])
+		[optionsDictionary setObject:@"DSR" forKey:AMSerialOptionOutputFlowControl];
+	if ([self CAROutputFlowControl])
+		[optionsDictionary setObject:@"CAR" forKey:AMSerialOptionOutputFlowControl];
+	
+	if ([self echoEnabled])
+		[optionsDictionary setObject:@"YES" forKey:AMSerialOptionEcho];
+
+	if ([self canonicalMode])
+		[optionsDictionary setObject:@"YES" forKey:AMSerialOptionCanonicalMode];
+
+}
+
+
+- (NSDictionary *)options
+{
+	// will open the port to get options if neccessary
+	if ([optionsDictionary objectForKey:AMSerialOptionServiceName] == nil) {
+		if (fileDescriptor < 0) {
+			[self open];
+			[self close];
+		}
+		[self buildOptionsDictionary];
+	}
+	return [NSMutableDictionary dictionaryWithDictionary:optionsDictionary];
+}
+
+- (void)setOptions:(NSDictionary *)newOptions
+{
+	// AMSerialOptionServiceName HAS to match! You may NOT switch ports using this
+	// method.
+	NSString *temp;
+	
+	if ([(NSString *)[newOptions objectForKey:AMSerialOptionServiceName] isEqualToString:[self name]]) {
+		[self clearError];
+		[optionsDictionary addEntriesFromDictionary:newOptions];
+		// parse dictionary
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionSpeed];
+		[self setSpeed:[temp intValue]];
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionDataBits];
+		[self setDataBits:[temp intValue]];
+		
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionParity];
+		if (temp == nil)
+			[self setParity:kAMSerialParityNone];
+		else if ([temp isEqualToString:@"Odd"])
+			[self setParity:kAMSerialParityOdd];
+		else
+			[self setParity:kAMSerialParityEven];
+		
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionStopBits];
+		int		numStopBits = [temp intValue];
+		[self setStopBits:(AMSerialStopBits)numStopBits];
+		
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionInputFlowControl];
+		[self setRTSInputFlowControl:[temp isEqualToString:@"RTS"]];
+		[self setDTRInputFlowControl:[temp isEqualToString:@"DTR"]];
+		
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionOutputFlowControl];
+		[self setCTSOutputFlowControl:[temp isEqualToString:@"CTS"]];
+		[self setDSROutputFlowControl:[temp isEqualToString:@"DSR"]];
+		[self setCAROutputFlowControl:[temp isEqualToString:@"CAR"]];
+		
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionEcho];
+		[self setEchoEnabled:(temp != nil)];
+
+		temp = (NSString *)[optionsDictionary objectForKey:AMSerialOptionCanonicalMode];
+		[self setCanonicalMode:(temp != nil)];
+
+		[self commitChanges];
+	} else {
+#ifdef AMSerialDebug
+		NSLog(@"Error setting options for port %s (wrong port name: %s).\n", [self name], [newOptions objectForKey:AMSerialOptionServiceName]);
+#endif
+	}
+}
+
+
+- (long)speed
+{
+	return cfgetospeed(options);	// we should support cfgetispeed too
+}
+
+- (BOOL)setSpeed:(long)speed
+{
+	BOOL result = YES;
+	int errorCode = 0;
+	
+	options->c_ospeed = speed;
+	options->c_ispeed = speed;
+	
+#if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4)
+	// Starting with Tiger, the IOSSIOSPEED ioctl can be used to set arbitrary baud rates
+	// other than those specified by POSIX. The driver for the underlying serial hardware
+	// ultimately determines which baud rates can be used. This ioctl sets both the input
+	// and output speed. 
+	
+	speed_t newSpeed = speed;
+	if (fileDescriptor >= 0) {
+		errorCode = ioctl(fileDescriptor, IOSSIOSPEED, &newSpeed);
+	} else {
+		result = NO;
+		gotError = YES;
+		lastError = EBADF; // Bad file descriptor
+	}
+#else
+	// set both the input and output speed
+	errorCode = cfsetospeed(options, speed);
+	errorCode = cfsetispeed(options, speed);
+#endif
+	if (errorCode == -1) {
+		result = NO;
+		gotError = YES;
+		lastError = errno;
+	}
+	return result;
+}
+
+
+- (unsigned long)dataBits
+{
+	return 5 + ((options->c_cflag & CSIZE) >> 8);
+	// man ... I *hate* C syntax ...
+}
+
+- (void)setDataBits:(unsigned long)bits	// 5 to 8 (5 is marked as "(pseudo)")
+{
+	// ?? options->c_oflag &= ~OPOST;
+	options->c_cflag &= ~CSIZE;
+	switch (bits) {
+		case 5:	options->c_cflag |= CS5;	// redundant since CS5 == 0
+			break;
+		case 6:	options->c_cflag |= CS6;
+			break;
+		case 7:	options->c_cflag |= CS7;
+			break;
+		case 8:	options->c_cflag |= CS8;
+			break;
+	}
+}
+
+
+- (AMSerialParity)parity
+{
+	AMSerialParity result;
+	if (options->c_cflag & PARENB) {
+		if (options->c_cflag & PARODD) {
+			result = kAMSerialParityOdd;
+		} else {
+			result = kAMSerialParityEven;
+		}
+	} else {
+		result = kAMSerialParityNone;
+	}
+	return result;
+}
+
+- (void)setParity:(AMSerialParity)newParity
+{
+	switch (newParity) {
+		case kAMSerialParityNone: {
+			options->c_cflag &= ~PARENB;
+			break;
+		}
+		case kAMSerialParityOdd: {
+			options->c_cflag |= PARENB;
+			options->c_cflag |= PARODD;
+			break;
+		}
+		case kAMSerialParityEven: {
+			options->c_cflag |= PARENB;
+			options->c_cflag &= ~PARODD;
+			break;
+		}
+	}
+}
+
+
+- (AMSerialStopBits)stopBits
+{
+	if (options->c_cflag & CSTOPB)
+		return kAMSerialStopBitsTwo;
+	else
+		return kAMSerialStopBitsOne;
+}
+
+- (void)setStopBits:(AMSerialStopBits)numBits
+{
+	if (numBits == kAMSerialStopBitsOne)
+		options->c_cflag &= ~CSTOPB;
+	else if (numBits == kAMSerialStopBitsTwo)
+		options->c_cflag |= CSTOPB;
+}
+
+
+- (BOOL)echoEnabled
+{
+	return (options->c_lflag & ECHO);
+}
+
+- (void)setEchoEnabled:(BOOL)echo
+{
+	if (echo == YES)
+		options->c_lflag |= ECHO;
+	else
+		options->c_lflag &= ~ECHO;
+}
+
+
+- (BOOL)RTSInputFlowControl
+{
+	return (options->c_cflag & CRTS_IFLOW);
+}
+
+- (void)setRTSInputFlowControl:(BOOL)rts
+{
+	if (rts == YES)
+		options->c_cflag |= CRTS_IFLOW;
+	else
+		options->c_cflag &= ~CRTS_IFLOW;
+}
+
+
+- (BOOL)DTRInputFlowControl
+{
+	return (options->c_cflag & CDTR_IFLOW);
+}
+
+- (void)setDTRInputFlowControl:(BOOL)dtr
+{
+	if (dtr == YES)
+		options->c_cflag |= CDTR_IFLOW;
+	else
+		options->c_cflag &= ~CDTR_IFLOW;
+}
+
+
+- (BOOL)CTSOutputFlowControl
+{
+	return (options->c_cflag & CCTS_OFLOW);
+}
+
+- (void)setCTSOutputFlowControl:(BOOL)cts
+{
+	if (cts == YES)
+		options->c_cflag |= CCTS_OFLOW;
+	else
+		options->c_cflag &= ~CCTS_OFLOW;
+}
+
+
+- (BOOL)DSROutputFlowControl
+{
+	return (options->c_cflag & CDSR_OFLOW);
+}
+
+- (void)setDSROutputFlowControl:(BOOL)dsr
+{
+	if (dsr == YES)
+		options->c_cflag |= CDSR_OFLOW;
+	else
+		options->c_cflag &= ~CDSR_OFLOW;
+}
+
+
+- (BOOL)CAROutputFlowControl
+{
+	return (options->c_cflag & CCAR_OFLOW);
+}
+
+- (void)setCAROutputFlowControl:(BOOL)car
+{
+	if (car == YES)
+		options->c_cflag |= CCAR_OFLOW;
+	else
+		options->c_cflag &= ~CCAR_OFLOW;
+}
+
+
+- (BOOL)hangupOnClose
+{
+	return (options->c_cflag & HUPCL);
+}
+
+- (void)setHangupOnClose:(BOOL)hangup
+{
+	if (hangup == YES)
+		options->c_cflag |= HUPCL;
+	else
+		options->c_cflag &= ~HUPCL;
+}
+
+- (BOOL)localMode
+{
+	return (options->c_cflag & CLOCAL);
+}
+
+- (void)setLocalMode:(BOOL)local
+{
+	// YES = ignore modem status lines
+	if (local == YES)
+		options->c_cflag |= CLOCAL;
+	else
+		options->c_cflag &= ~CLOCAL;
+}
+
+- (BOOL)canonicalMode
+{
+	return (options->c_lflag & ICANON);
+}
+
+- (void)setCanonicalMode:(BOOL)flag
+{
+	if (flag == YES)
+		options->c_lflag |= ICANON;
+	else
+		options->c_lflag &= ~ICANON;
+}
+
+- (char)endOfLineCharacter
+{
+	return options->c_cc[VEOL];
+}
+
+- (void)setEndOfLineCharacter:(char)eol
+{
+	options->c_cc[VEOL] = eol;
+}
+
+- (void)clearError
+{
+	// call this before changing any settings
+	gotError = NO;
+}
+
+- (BOOL)commitChanges
+{
+	// call this after using any of the setters above
+	if (gotError)
+		return NO;
+	
+	if (tcsetattr(fileDescriptor, TCSANOW, options) == -1) {
+		// something went wrong
+		gotError = YES;
+		lastError = errno;
+		return NO;
+	} else {
+		[self buildOptionsDictionary];
+		return YES;
+	}
+}
+
+- (int)errorCode
+{
+	// if -commitChanges returns NO, look here for further info
+	return lastError;
+}
+
+- (NSTimeInterval)readTimeout
+{
+    return readTimeout;
+}
+
+- (void)setReadTimeout:(NSTimeInterval)aReadTimeout
+{
+    readTimeout = aReadTimeout;
+}
+
+// private methods
+
+- (void)readTimeoutAsTimeval:(struct timeval*)timeout
+{
+	NSTimeInterval timeoutInterval = [self readTimeout];
+	double numSecs = trunc(timeoutInterval);
+	double numUSecs = (timeoutInterval-numSecs)*1000000.0;
+	timeout->tv_sec = (time_t)lrint(numSecs);
+	timeout->tv_usec = (suseconds_t)lrint(numUSecs);
+}
+
+
+@end

File ArduinoSerialXcode/ArduinoSerial/AMSerialPortAdditions.h

+//
+//  AMSerialPortAdditions.h
+//
+//  Created by Andreas on Thu May 02 2002.
+//  Copyright (c) 2001 Andreas Mayer. All rights reserved.
+//
+//  2002-10-04 Andreas Mayer
+//  - readDataInBackgroundWithTarget:selector: and writeDataInBackground: added
+//  2002-10-10 Andreas Mayer
+//	- stopWriteInBackground added
+//  2002-10-17 Andreas Mayer
+//	- numberOfWriteInBackgroundThreads added
+//  2002-10-25 Andreas Mayer
+//	- readDataInBackground and stopReadInBackground added
+//  2004-02-10 Andreas Mayer
+//    - replaced notifications for background reading/writing with direct messages to delegate
+//      see informal protocol
+//  2004-08-18 Andreas Mayer
+//	- readStringOfLength: added (suggested by Michael Beck)
+//  2006-08-16 Andreas Mayer / Sean McBride
+//	- changed interface for blocking read/write access significantly
+//	- fixed -checkRead and renamed it to -bytesAvailable
+//	- see AMSerialPort_Deprecated for old interfaces
+//  2007-10-26 Sean McBride
+//  - made code 64 bit and garbage collection clean
+
+#import "AMSDKCompatibility.h"
+
+#import <Foundation/Foundation.h>
+#import "AMSerialPort.h"
+
+
+@interface AMSerialPort (AMSerialPortAdditions)
+
+// returns the number of bytes available in the input buffer
+// Be careful how you use this information, it may be out of date just after you get it
+- (int)bytesAvailable;
+
+- (void)waitForInput:(id)target selector:(SEL)selector;
+
+
+// all blocking reads returns after [self readTimout] seconds elapse, at the latest
+- (NSData *)readAndReturnError:(NSError **)error;
+
+// returns after 'bytes' bytes are read
+- (NSData *)readBytes:(NSUInteger)bytes error:(NSError **)error;
+
+// returns when 'stopChar' is encountered
+- (NSData *)readUpToChar:(char)stopChar error:(NSError **)error;
+
+// returns after 'bytes' bytes are read or if 'stopChar' is encountered, whatever comes first
+- (NSData *)readBytes:(NSUInteger)bytes upToChar:(char)stopChar error:(NSError **)error;
+
+// data read will be converted into an NSString, using the given encoding
+// NOTE: encodings that take up more than one byte per character may fail if only a part of the final string was received
+- (NSString *)readStringUsingEncoding:(NSStringEncoding)encoding error:(NSError **)error;
+
+- (NSString *)readBytes:(NSUInteger)bytes usingEncoding:(NSStringEncoding)encoding error:(NSError **)error;
+
+// NOTE: 'stopChar' has to be a byte value, using the given encoding; you can not wait for an arbitrary character from a multi-byte encoding
+- (NSString *)readUpToChar:(char)stopChar usingEncoding:(NSStringEncoding)encoding error:(NSError **)error;
+
+- (NSString *)readBytes:(NSUInteger)bytes upToChar:(char)stopChar usingEncoding:(NSStringEncoding)encoding error:(NSError **)error;
+
+// write to the serial port; NO if an error occured
+- (BOOL)writeData:(NSData *)data error:(NSError **)error;
+
+- (BOOL)writeString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(NSError **)error;
+
+
+- (void)readDataInBackground;
+//
+// Will send serialPortReadData: to delegate
+// the dataDictionary object will contain these entries:
+// 1. "serialPort": the AMSerialPort object that sent the message
+// 2. "data": (NSData *)data - received data
+
+- (void)stopReadInBackground;
+
+- (void)writeDataInBackground:(NSData *)data;
+//
+// Will send serialPortWriteProgress: to delegate if task lasts more than
+// approximately three seconds.
+// the dataDictionary object will contain these entries:
+// 1. "serialPort": the AMSerialPort object that sent the message
+// 2. "value": (NSNumber *)value - bytes sent
+// 3. "total": (NSNumber *)total - bytes total
+
+- (void)stopWriteInBackground;
+
+- (int)numberOfWriteInBackgroundThreads;
+
+
+@end

File ArduinoSerialXcode/ArduinoSerial/AMSerialPortAdditions.m

+//
+//  AMSerialPortAdditions.m
+//
+//  Created by Andreas on Thu May 02 2002.
+//  Copyright (c) 2001 Andreas Mayer. All rights reserved.
+//
+//  2002-07-02 Andreas Mayer
+//	- initialize buffer in readString
+//  2002-10-04 Andreas Mayer
+//  - readDataInBackgroundWithTarget:selector: and writeDataInBackground: added
+//  2002-10-10 Andreas Mayer
+//	- stopWriteInBackground added
+//	- send notifications about sent data through distributed notification center
+//  2002-10-17 Andreas Mayer
+//	- numberOfWriteInBackgroundThreads added
+//	- if total write time will exceed 3 seconds, send
+//		CommXWriteInBackgroundProgressNotification without delay
+//  2002-10-25 Andreas Mayer
+//	- readDataInBackground and stopReadInBackground added
+//  2004-08-18 Andreas Mayer
+//	- readStringOfLength: added (suggested by Michael Beck)
+//  2005-04-11 Andreas Mayer
+//	-  attempt at a fix for readDataInBackgroundThread - fileDescriptor could already be closed
+//		(thanks to David Bainbridge for the bug report) does not work as of yet
+//  2007-10-26 Sean McBride
+//  - made code 64 bit and garbage collection clean
+
+
+#import "AMSDKCompatibility.h"
+
+#import <sys/ioctl.h>
+#import <sys/filio.h>
+
+#import "AMSerialPortAdditions.h"
+#import "AMSerialErrors.h"
+
+
+@interface AMSerialPort (AMSerialPortAdditionsPrivate)
+- (void)readDataInBackgroundThread;
+- (void)writeDataInBackgroundThread:(NSData *)data;
+- (id)am_readTarget;
+- (void)am_setReadTarget:(id)newReadTarget;
+- (NSData *)readAndStopAfterBytes:(BOOL)stopAfterBytes bytes:(NSUInteger)bytes stopAtChar:(BOOL)stopAtChar stopChar:(char)stopChar error:(NSError **)error;
+- (void)reportProgress:(NSUInteger)progress dataLen:(NSUInteger)dataLen;
+@end
+
+
+@implementation AMSerialPort (AMSerialPortAdditions)
+
+
+// ============================================================
+#pragma mark -
+#pragma mark blocking IO
+// ============================================================
+
+- (void)doRead:(NSTimer *)timer
+{
+	(void)timer;
+	
+#ifdef AMSerialDebug
+	NSLog(@"doRead");
+#endif
+	int res;
+	struct timeval timeout;
+	if (fileDescriptor >= 0) {
+		FD_ZERO(readfds);
+		FD_SET(fileDescriptor, readfds);
+		[self readTimeoutAsTimeval:&timeout];
+		res = select(fileDescriptor+1, readfds, nil, nil, &timeout);
+		if (res >= 1) {
+			NSString *readStr = [self readStringUsingEncoding:NSUTF8StringEncoding error:NULL];
+			[[self am_readTarget] performSelector:am_readSelector withObject:readStr];
+			[self am_setReadTarget:nil];
+		} else {
+			[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(doRead:) userInfo:self repeats:NO];
+		}
+	} else {
+		// file already closed
+		[self am_setReadTarget:nil];
+	}
+}
+
+// all blocking reads returns after [self readTimout] seconds elapse, at the latest
+- (NSData *)readAndReturnError:(NSError **)error
+{
+	NSData *result = [self readAndStopAfterBytes:NO bytes:0 stopAtChar:NO stopChar:0 error:error];
+	return result;
+}
+
+// returns after 'bytes' bytes are read
+- (NSData *)readBytes:(NSUInteger)bytes error:(NSError **)error
+{
+	NSData *result = [self readAndStopAfterBytes:YES bytes:bytes stopAtChar:NO stopChar:0 error:error];
+	return result;
+}
+
+// returns when 'stopChar' is encountered
+- (NSData *)readUpToChar:(char)stopChar error:(NSError **)error
+{
+	NSData *result = [self readAndStopAfterBytes:NO bytes:0 stopAtChar:YES stopChar:stopChar error:error];
+	return result;
+}
+
+// returns after 'bytes' bytes are read or if 'stopChar' is encountered, whatever comes first
+- (NSData *)readBytes:(NSUInteger)bytes upToChar:(char)stopChar error:(NSError **)error
+{
+	NSData *result = [self readAndStopAfterBytes:YES bytes:bytes stopAtChar:YES stopChar:stopChar error:error];
+	return result;
+}
+
+// data read will be converted into an NSString, using the given encoding
+// NOTE: encodings that take up more than one byte per character may fail if only a part of the final string was received
+- (NSString *)readStringUsingEncoding:(NSStringEncoding)encoding error:(NSError **)error
+{
+	NSString *result = nil;
+	NSData *data = [self readAndStopAfterBytes:NO bytes:0 stopAtChar:NO stopChar:0 error:error];
+	if (data) {
+		result = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
+	}
+	return result;
+}
+
+- (NSString *)readBytes:(NSUInteger)bytes usingEncoding:(NSStringEncoding)encoding error:(NSError **)error
+{
+	NSString *result = nil;
+	NSData *data = [self readAndStopAfterBytes:YES bytes:bytes stopAtChar:NO stopChar:0 error:error];
+	if (data) {
+		result = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
+	}
+	return result;
+}
+
+// NOTE: 'stopChar' has to be a byte value, using the given encoding; you can not wait for an arbitrary character from a multi-byte encoding
+- (NSString *)readUpToChar:(char)stopChar usingEncoding:(NSStringEncoding)encoding error:(NSError **)error
+{
+	NSString *result = nil;
+	NSData *data = [self readAndStopAfterBytes:NO bytes:0 stopAtChar:YES stopChar:stopChar error:error];
+	if (data) {
+		result = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
+	}
+	return result;
+}
+
+- (NSString *)readBytes:(NSUInteger)bytes upToChar:(char)stopChar usingEncoding:(NSStringEncoding)encoding error:(NSError **)error
+{
+	NSString *result = nil;
+	NSData *data = [self readAndStopAfterBytes:YES bytes:bytes stopAtChar:YES stopChar:stopChar error:error];
+	if (data) {
+		result = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
+	}
+	return result;
+}
+
+
+// write to the serial port; NO if an error occured
+- (BOOL)writeData:(NSData *)data error:(NSError **)error
+{
+	BOOL result = NO;
+
+	const char *dataBytes = (const char*)[data bytes];
+	NSUInteger dataLen = [data length];
+	ssize_t bytesWritten = 0;
+	int errorCode = kAMSerialErrorNone;
+	if (dataBytes && (dataLen > 0)) {
+		bytesWritten = write(fileDescriptor, dataBytes, dataLen);
+		if (bytesWritten < 0) {
+			errorCode = kAMSerialErrorFatal;
+		} else if ((NSUInteger)bytesWritten == dataLen) {
+			result = YES;
+		} else {
+			errorCode = kAMSerialErrorOnlySomeDataWritten;
+		}
+	} else {
+		errorCode = kAMSerialErrorNoDataToWrite;
+	}
+	if (error) {
+		NSDictionary *userInfo = nil;
+		if (bytesWritten > 0) {
+			NSNumber* bytesWrittenNum = [NSNumber numberWithUnsignedLongLong:bytesWritten];
+			userInfo = [NSDictionary dictionaryWithObject:bytesWrittenNum forKey:@"bytesWritten"];
+		}
+		*error = [NSError errorWithDomain:AMSerialErrorDomain code:errorCode userInfo:userInfo];
+	}
+		
+	return result;
+}
+
+- (BOOL)writeString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(NSError **)error
+{
+	NSData *data = [string dataUsingEncoding:encoding];
+	return [self writeData:data error:error];
+}
+
+- (int)bytesAvailable
+{
+#ifdef AMSerialDebug
+	NSLog(@"bytesAvailable");
+#endif
+
+	// yes, that cast is correct.  ioctl() is declared to take a char* but should be void* as really it
+	// depends on the 2nd parameter.  Ahhh, I love crappy old UNIX APIs :)
+	int result = 0;
+	int err = ioctl(fileDescriptor, FIONREAD, (char *)&result);
+	if (err != 0) {
+		result = -1;
+	}
+	return result;
+}
+
+
+- (void)waitForInput:(id)target selector:(SEL)selector
+{
+#ifdef AMSerialDebug
+	NSLog(@"waitForInput");
+#endif
+	[self am_setReadTarget:target];
+	am_readSelector = selector;
+	[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(doRead:) userInfo:self repeats:NO];
+}
+
+// ============================================================
+#pragma mark -
+#pragma mark threaded IO
+// ============================================================
+
+- (void)readDataInBackground
+{
+#ifdef AMSerialDebug
+	NSLog(@"readDataInBackground");
+#endif
+	if (delegateHandlesReadInBackground) {
+		countReadInBackgroundThreads++;
+		[NSThread detachNewThreadSelector:@selector(readDataInBackgroundThread) toTarget:self withObject:nil];
+	} else {
+		// ... throw exception?
+	}
+}
+
+- (void)stopReadInBackground
+{
+#ifdef AMSerialDebug
+	NSLog(@"stopReadInBackground");
+#endif
+	stopReadInBackground = YES;
+}
+
+- (void)writeDataInBackground:(NSData *)data
+{
+#ifdef AMSerialDebug
+	NSLog(@"writeDataInBackground");
+#endif
+	if (delegateHandlesWriteInBackground) {
+		countWriteInBackgroundThreads++;
+		[NSThread detachNewThreadSelector:@selector(writeDataInBackgroundThread:) toTarget:self withObject:data];
+	} else {
+		// ... throw exception?
+	}
+}
+
+- (void)stopWriteInBackground
+{
+#ifdef AMSerialDebug
+	NSLog(@"stopWriteInBackground");
+#endif
+	stopWriteInBackground = YES;
+}
+
+- (int)numberOfWriteInBackgroundThreads
+{
+	return countWriteInBackgroundThreads;
+}
+
+
+@end
+
+#pragma mark -
+
+@implementation AMSerialPort (AMSerialPortAdditionsPrivate)
+
+// ============================================================
+#pragma mark -
+#pragma mark threaded methods
+// ============================================================
+
+- (void)readDataInBackgroundThread
+{
+	NSData *data = nil;
+	void *localBuffer;
+	ssize_t bytesRead = 0;
+	fd_set *localReadFDs = NULL;
+
+	[readLock lock];	// read in sequence
+	//NSLog(@"readDataInBackgroundThread - [readLock lock]");
+
+	localBuffer = malloc(AMSER_MAXBUFSIZE);
+	stopReadInBackground = NO;
+	NSAutoreleasePool *localAutoreleasePool = [[NSAutoreleasePool alloc] init];
+	[closeLock lock];
+	if ((fileDescriptor >= 0) && (!stopReadInBackground)) {
+		//NSLog(@"readDataInBackgroundThread - [closeLock lock]");
+		localReadFDs = (fd_set*)malloc(sizeof(fd_set));
+		FD_ZERO(localReadFDs);
+		FD_SET(fileDescriptor, localReadFDs);
+		[closeLock unlock];
+		//NSLog(@"readDataInBackgroundThread - [closeLock unlock]");
+		int res = select(fileDescriptor+1, localReadFDs, nil, nil, nil); // timeout);
+		if ((res >= 1) && (fileDescriptor >= 0)) {
+			//bytesRead = read(fileDescriptor, localBuffer, AMSER_MAXBUFSIZE);
+		}
+		//FIXME: This was modified on 3-20-09 by Pat O'Keefe for a particular application...swap comments for normal operation
+		//data = [NSData dataWithBytes:localBuffer length:bytesRead];
+		data = [self readUpToChar:'\n' error:NULL];
+		[delegate performSelectorOnMainThread:@selector(serialPortReadData:) withObject:[NSDictionary dictionaryWithObjectsAndKeys: self, @"serialPort", data, @"data", nil] waitUntilDone:YES];//was NO
+	} else {
+		[closeLock unlock];
+	}
+	[localAutoreleasePool release];
+	if (localReadFDs)
+		free(localReadFDs);
+	if (localBuffer)
+		free(localBuffer);
+
+	countReadInBackgroundThreads--;
+
+	[readLock unlock];
+	//NSLog(@"readDataInBackgroundThread - [readLock unlock]");
+
+}
+
+/* new version - does not work yet
+- (void)readDataInBackgroundThread
+{
+	NSData *data = nil;
+	void *localBuffer;
+	int bytesRead = 0;
+	fd_set *localReadFDs;
+
+#ifdef AMSerialDebug
+	NSLog(@"readDataInBackgroundThread: %@", [NSThread currentThread]);
+#endif
+	localBuffer = malloc(AMSER_MAXBUFSIZE);
+	[stopReadInBackgroundLock lock];
+	stopReadInBackground = NO;
+	//NSLog(@"stopReadInBackground set to NO: %@", [NSThread currentThread]);
+	[stopReadInBackgroundLock unlock];
+	//NSLog(@"attempt readLock: %@", [NSThread currentThread]);
+	[readLock lock];	// write in sequence
+	//NSLog(@"readLock locked: %@", [NSThread currentThread]);
+	//NSLog(@"attempt closeLock: %@", [NSThread currentThread]);
+	[closeLock lock];
+	//NSLog(@"closeLock locked: %@", [NSThread currentThread]);
+	if (!stopReadInBackground && (fileDescriptor >= 0)) {
+		NSAutoreleasePool *localAutoreleasePool = [[NSAutoreleasePool alloc] init];
+		localReadFDs = malloc(sizeof(*localReadFDs));
+		FD_ZERO(localReadFDs);
+		FD_SET(fileDescriptor, localReadFDs);
+		int res = select(fileDescriptor+1, localReadFDs, nil, nil, nil); // timeout);
+		if (res >= 1) {
+#ifdef AMSerialDebug
+			NSLog(@"attempt read: %@", [NSThread currentThread]);
+#endif
+			bytesRead = read(fileDescriptor, localBuffer, AMSER_MAXBUFSIZE);
+		}
+#ifdef AMSerialDebug
+		NSLog(@"data read: %@", [NSThread currentThread]);
+#endif
+		data = [NSData dataWithBytes:localBuffer length:bytesRead];
+#ifdef AMSerialDebug
+		NSLog(@"send AMSerialReadInBackgroundDataMessage");
+#endif
+		[delegate performSelectorOnMainThread:@selector(serialPortReadData:) withObject:[NSDictionary dictionaryWithObjectsAndKeys: self, @"serialPort", data, @"data", nil] waitUntilDone:NO];
+		free(localReadFDs);
+		[localAutoreleasePool release];
+	} else {
+#ifdef AMSerialDebug
+		NSLog(@"read stopped: %@", [NSThread currentThread]);
+#endif
+	}
+
+	[closeLock unlock];
+	//NSLog(@"closeLock unlocked: %@", [NSThread currentThread]);
+	[readLock unlock];
+	//NSLog(@"readLock unlocked: %@", [NSThread currentThread]);
+	[countReadInBackgroundThreadsLock lock];
+	countReadInBackgroundThreads--;
+	[countReadInBackgroundThreadsLock unlock];
+	
+	free(localBuffer);
+}
+*/
+
+- (void)writeDataInBackgroundThread:(NSData *)data
+{
+	
+#ifdef AMSerialDebug
+	NSLog(@"writeDataInBackgroundThread");
+#endif
+	void *localBuffer;
+	NSUInteger pos;
+	NSUInteger bufferLen;
+	NSUInteger dataLen;
+	ssize_t written;
+	NSDate *nextNotificationDate;
+	BOOL notificationSent = NO;
+	long speed;
+	long estimatedTime;
+	BOOL error = NO;
+	
+	NSAutoreleasePool *localAutoreleasePool = [[NSAutoreleasePool alloc] init];
+
+	[data retain];
+	localBuffer = malloc(AMSER_MAXBUFSIZE);
+	stopWriteInBackground = NO;
+	[writeLock lock];	// write in sequence
+	pos = 0;
+	dataLen = [data length];
+	speed = [self speed];
+	estimatedTime = (dataLen*8)/speed;
+	if (estimatedTime > 3) { // will take more than 3 seconds
+		notificationSent = YES;
+		[self reportProgress:pos dataLen:dataLen];
+		nextNotificationDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
+	} else {
+		nextNotificationDate = [NSDate dateWithTimeIntervalSinceNow:2.0];
+	}
+	while (!stopWriteInBackground && (pos < dataLen) && !error) {
+		bufferLen = MIN(AMSER_MAXBUFSIZE, dataLen-pos);
+
+		[data getBytes:localBuffer range:NSMakeRange(pos, bufferLen)];
+		written = write(fileDescriptor, localBuffer, bufferLen);
+		error = (written == 0); // error condition
+		if (error)
+			break;
+		pos += written;
+
+		if ([(NSDate *)[NSDate date] compare:nextNotificationDate] == NSOrderedDescending) {
+			if (notificationSent || (pos < dataLen)) { // not for last block only
+				notificationSent = YES;
+				[self reportProgress:pos dataLen:dataLen];
+				nextNotificationDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
+			}
+		}
+	}
+	if (notificationSent) {
+		[self reportProgress:pos dataLen:dataLen];
+	}
+	stopWriteInBackground = NO;
+	[writeLock unlock];
+	countWriteInBackgroundThreads--;
+	
+	free(localBuffer);
+	[data release];
+	[localAutoreleasePool release];
+}
+
+- (id)am_readTarget
+{
+	return am_readTarget; 
+}
+
+- (void)am_setReadTarget:(id)newReadTarget
+{
+	if (am_readTarget != newReadTarget) {
+		[newReadTarget retain];
+		[am_readTarget release];
+		am_readTarget = newReadTarget;
+	}
+}
+
+// Low-level blocking read method.
+// This method reads from the serial port and blocks as necessary, it returns when:
+//  - [self readTimeout] seconds has elapsed
+//  - if stopAfterBytes is YES, when 'bytesToRead' bytes have been read
+//  - if stopAtChar is YES, when 'stopChar' is found at the end of the read buffer
+//  - a fatal error occurs
+//
+// Upon return: as long as some data was actually read, and no serious error occured, an autoreleased NSData
+// object with that data is created and returned, otherwise nil is.
+- (NSData *)readAndStopAfterBytes:(BOOL)stopAfterBytes bytes:(NSUInteger)bytesToRead stopAtChar:(BOOL)stopAtChar stopChar:(char)stopChar error:(NSError **)error
+{
+	NSData *result = nil;
+	
+	struct timeval timeout;
+	NSUInteger bytesRead = 0;
+	BOOL stop = NO;
+	int errorCode = kAMSerialErrorNone;
+	int endCode = kAMSerialEndOfStream;
+	NSError *underlyingError = nil;
+	
+	// Note the time that we start
+	NSDate *startTime = [NSDate date];
+	
+	// How long, in total, do we block before timing out?
+	NSTimeInterval totalTimeout = [self readTimeout];
+
+	// This value will be decreased each time through the loop
+	NSTimeInterval remainingTimeout = totalTimeout;
+	
+	while (!stop) {
+		if (remainingTimeout <= 0.0) {
+			stop = YES;
+			errorCode = kAMSerialErrorTimeout;
+			break;
+		} else {
+			// Convert from NSTimeInterval to struct timeval
+			double numSecs = trunc(remainingTimeout);
+			double numUSecs = (remainingTimeout-numSecs)*1000000.0;
+			timeout.tv_sec = (time_t)lrint(numSecs);
+			timeout.tv_usec = (suseconds_t)lrint(numUSecs);
+#ifdef AMSerialDebug
+			NSLog(@"timeout: %fs = %ds and %dus", remainingTimeout, timeout.tv_sec, timeout.tv_usec);
+#endif
+			
+			// If the remaining time is so small that it has rounded to zero, bump it up to 1 microsec.
+			// Why?  Because passing a zeroed timeval to select() indicates that we want to poll, but we don't.
+			if ((timeout.tv_sec == 0) && (timeout.tv_usec == 0)) {
+				timeout.tv_usec = 1;
+			}
+			FD_ZERO(readfds);
+			FD_SET(fileDescriptor, readfds);
+			[self readTimeoutAsTimeval:&timeout];
+			int selectResult = select(fileDescriptor+1, readfds, NULL, NULL, &timeout);
+			if (selectResult == -1) {
+				stop = YES;
+				errorCode = kAMSerialErrorFatal;
+				break;
+			} else if (selectResult == 0) {
+				stop = YES;
+				errorCode = kAMSerialErrorTimeout;
+				break;
+			} else {
+				size_t	sizeToRead;
+				if (stopAfterBytes) {
+					sizeToRead = (MIN(bytesToRead, AMSER_MAXBUFSIZE))-bytesRead;
+				} else {
+					sizeToRead = AMSER_MAXBUFSIZE-bytesRead;
+				}
+				ssize_t	readResult = read(fileDescriptor, buffer+bytesRead, sizeToRead);
+				if (readResult > 0) {
+					bytesRead += readResult;
+					if (stopAfterBytes) {
+						if (bytesRead == bytesToRead) {
+							stop = YES;
+							endCode = kAMSerialStopLengthReached;
+							break;
+						} else if (bytesRead > bytesToRead) {
+							stop = YES;
+							endCode = kAMSerialStopLengthExceeded;
+							break;
+						}
+					}
+					if (stopAtChar && (buffer[bytesRead-1] == stopChar)) {
+						stop = YES;
+						endCode = kAMSerialStopCharReached;
+						break;
+					}
+					if (bytesRead >= AMSER_MAXBUFSIZE) {
+						stop = YES;
+						errorCode = kAMSerialErrorInternalBufferFull;
+						break;
+					}
+				} else if (readResult == 0) {
+					// Should not be possible since select() has indicated data is available
+					stop = YES;
+					errorCode = kAMSerialErrorFatal;
+					break;
+				} else {
+					stop = YES;
+					// Make underlying error
+					underlyingError = [NSError errorWithDomain:NSPOSIXErrorDomain code:readResult userInfo:nil];
+					errorCode = kAMSerialErrorFatal;
+					break;
+				}
+			}
+			
+			// Reduce the timeout value by the amount of time actually spent so far
+			remainingTimeout = totalTimeout - [[NSDate date] timeIntervalSinceDate:startTime];
+		}
+	}
+	
+	if (error) {
+		NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+		[userInfo setObject:[NSNumber numberWithUnsignedLongLong:bytesRead] forKey:@"bytesRead"];
+		if (underlyingError) {
+			[userInfo setObject:underlyingError forKey:NSUnderlyingErrorKey];
+		}
+		if (errorCode == kAMSerialErrorNone) {
+			[userInfo setObject:[NSNumber numberWithInt:endCode] forKey:@"endCode"];
+		}
+		*error = [NSError errorWithDomain:AMSerialErrorDomain code:errorCode userInfo:userInfo];
+	}
+	if ((bytesRead > 0) && (errorCode != kAMSerialErrorFatal)) {
+		result = [NSData dataWithBytes:buffer length:bytesRead];
+	}
+	
+	return result;
+}
+
+- (void)reportProgress:(NSUInteger)progress dataLen:(NSUInteger)dataLen
+{
+#ifdef AMSerialDebug
+	NSLog(@"send AMSerialWriteInBackgroundProgressMessage");
+#endif
+	[delegate performSelectorOnMainThread:@selector(serialPortWriteProgress:) withObject:
+		[NSDictionary dictionaryWithObjectsAndKeys:
+			self, @"serialPort",
+			[NSNumber numberWithUnsignedLongLong:progress], @"value",
+			[NSNumber numberWithUnsignedLongLong:dataLen], @"total", nil]
+		waitUntilDone:NO];
+}
+
+@end

File ArduinoSerialXcode/ArduinoSerial/AMS