mac下串口通訊工具的編寫

ORSSerialPort.h



#import <Foundation/Foundation.h>
#import <IOKit/IOTypes.h>
#import <termios.h>

//#define LOG_SERIAL_PORT_ERRORS 

enum {
	ORSSerialPortParityNone = 0,
	ORSSerialPortParityOdd,
	ORSSerialPortParityEven
}; typedef NSUInteger ORSSerialPortParity;

@protocol ORSSerialPortDelegate;

/**
 *  The ORSSerialPort class represents a serial port, and includes methods to
 *  configure, open and close a port, and send and receive data to and from
 *  a port.
 *
 *  There is a 1:1 correspondence between port devices on the
 *  system and instances of `ORSSerialPort`. That means that repeated requests
 *  for a port object for a given device or device path will return the same 
 *  instance of `ORSSerialPort`.
 *
 *  Opening a Port and Setting It Up
 *  --------------------------------
 *
 *  You can get an `ORSSerialPort` instance either of two ways. The easiest
 *  is to use `ORSSerialPortManager`'s `availablePorts` array. The other way
 *  is to get a new `ORSSerialPort` instance using the serial port's BSD device path:
 *
 *  	ORSSerialPort *port = [ORSSerialPort serialPortWithPath:@"/dev/cu.KeySerial1"];
 *
 *  Note that you must give `+serialPortWithPath:` the full path to the
 *  device, as shown in the example above.
 *
 *
 *  After you've got a port instance, you can open it with the `-open`
 *  method. When you're done using the port, close it using the `-close`
 *  method.
 *
 *  Port settings such as baud rate, number of stop bits, parity, and flow
 *  control settings can be set using the various properties `ORSSerialPort`
 *  provides. Note that all of these properties are Key Value Observing
 *  (KVO) compliant. This KVO compliance also applies to read-only
 *  properties for reading the state of the CTS, DSR and DCD pins. Among
 *  other things, this means it's easy to be notified when the state of one
 *  of these pins changes, without having to continually poll them, as well
 *  as making them easy to connect to a UI with Cocoa bindings.
 *
 *  Sending Data
 *  ------------
 *
 *  Send data by passing an `NSData` object to the `-sendData:` method:
 *
 *  	NSData *dataToSend = [self.sendTextField.stringValue dataUsingEncoding:NSUTF8StringEncoding];
 *  	[self.serialPort sendData:dataToSend];
 *
 *  Receiving Data
 *  --------------
 *
 *  To receive data, you must implement the `ORSSerialPortDelegate`
 *  protocol's `-serialPort:didReceiveData:` method, and set the
 *  `ORSSerialPort` instance's delegate property. As noted in the documentation
 *  for ORSSerialPortDelegate, this method is always called on the main queue.
 *  An example implementation is included below:
 *
 *  	- (void)serialPort:(ORSSerialPort *)serialPort didReceiveData:(NSData *)data
 *  	{
 *  		NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
 *  		[self.receivedDataTextView.textStorage.mutableString appendString:string];
 *  		[self.receivedDataTextView setNeedsDisplay:YES];
 *  	}
 */

@interface ORSSerialPort : NSObject

/** ---------------------------------------------------------------------------------------
 * @name Getting a Serial Port
 *  ---------------------------------------------------------------------------------------
 */

/**
 *  Returns an `ORSSerialPort` instance representing the serial port at `devicePath`.
 *
 *  `devicePath` must be the full, callout (cu.) or tty (tty.) path to an available
 *  serial port device on the system.
 *
 *  @param devicePath The full path (e.g. /dev/cu.usbserial) to the device.
 *
 *  @return An initalized `ORSSerialPort` instance, or nil if there was an error.
 * 
 *  @see -[ORSSerialPortManager availablePorts]
 *  @see -initWithPath:
 */
+ (ORSSerialPort *)serialPortWithPath:(NSString *)devicePath;

/**
 *  Returns an `ORSSerialPort` instance for the serial port represented by `device`.
 *
 *  Generally, `+serialPortWithPath:` is the method to use to get port instances
 *  programatically. This method may be useful if you're doing your own
 *  device discovery with IOKit functions, or otherwise have an IOKit port object
 *  you want to "turn into" an ORSSerialPort. Most people will not use this method
 *  directly.
 *
 *  @param device An IOKit port object representing the serial port device.
 *
 *  @return An initalized `ORSSerialPort` instance, or nil if there was an error.
 *
 *  @see -[ORSSerialPortManager availablePorts]
 *  @see +serialPortWithPath:
 */
+ (ORSSerialPort *)serialPortWithDevice:(io_object_t)device;

/**
 *  Returns an `ORSSerialPort` instance representing the serial port at `devicePath`.
 *
 *  `devicePath` must be the full, callout (cu.) or tty (tty.) path to an available
 *  serial port device on the system.
 *
 *  @param devicePath The full path (e.g. /dev/cu.usbserial) to the device.
 *
 *  @return An initalized `ORSSerialPort` instance, or nil if there was an error.
 *
 *  @see -[ORSSerialPortManager availablePorts]
 *  @see +serialPortWithPath:
 */
- (id)initWithPath:(NSString *)devicePath;

/**
 *  Returns an `ORSSerialPort` instance for the serial port represented by `device`.
 *
 *  Generally, `-initWithPath:` is the method to use to get port instances
 *  programatically. This method may be useful if you're doing your own
 *  device discovery with IOKit functions, or otherwise have an IOKit port object
 *  you want to "turn into" an ORSSerialPort. Most people will not use this method
 *  directly.
 *
 *  @param device An IOKit port object representing the serial port device.
 *
 *  @return An initalized `ORSSerialPort` instance, or nil if there was an error.
 *
 *  @see -[ORSSerialPortManager availablePorts]
 *  @see -initWithPath:
 */
- (id)initWithDevice:(io_object_t)device;

/** ---------------------------------------------------------------------------------------
 * @name Opening and Closing
 *  ---------------------------------------------------------------------------------------
 */

/**
 *  Opens the port represented by the receiver.
 *
 *  If this method succeeds, the ORSSerialPortDelegate method `-serialPortWasOpened:` will
 *  be called.
 *
 *  If opening the port fails, the ORSSerialPortDelegate method `-serialPort:didEncounterError:` will
 *  be called.
 */
- (BOOL)open;

/**
 *  Closes the port represented by the receiver.
 *
 *  If the port is closed successfully, the ORSSerialPortDelegate method `-serialPortWasClosed:` will
 *  be called before this method returns.
 *
 *  @return YES if closing the port was closed successfully, NO if closing the port failed.
 */
- (BOOL)close;

- (void)cleanup DEPRECATED_ATTRIBUTE; // Should never have been called in client code, anyway.

/**
 *  Closes the port and cleans up.
 *
 *  This method should never be called directly. Call `-close` to close a port instead.
 */
- (void)cleanupAfterSystemRemoval;

/** ---------------------------------------------------------------------------------------
 * @name Sending Data
 *  ---------------------------------------------------------------------------------------
 */

/**
 *  Sends data out through the serial port represented by the receiver.
 *
 *  This method attempts to send all data synchronously. If the serial port
 *  is unable to accept all the passed in data in a single write operation,
 *  The remaining data is buffered and sent later asynchronously.
 *
 *  If an error occurs, the ORSSerialPortDelegate method `-serialPort:didEncounterError:` will
 *  be called. The exception to this is if sending data fails because the port
 *  is closed. In that case, this method returns NO, but `-serialPort:didEncounterError:`
 *  is *not* called. You can ensure that the port is open by calling `-isOpen` before 
 *  calling this method.
 *
 *  @param data An `NSData` object containing the data to be sent.
 *
 *  @return YES if sending data failed, NO if an error occurred.
 */
- (BOOL)sendData:(NSData *)data;

/** ---------------------------------------------------------------------------------------
 * @name Delegate
 *  ---------------------------------------------------------------------------------------
 */

/**
 *  The delegate for the serial port object. Must implement the `ORSSerialPortDelegate` protocol.
 *
 */
@property (nonatomic, unsafe_unretained) id<ORSSerialPortDelegate> delegate;

/** ---------------------------------------------------------------------------------------
 * @name Port Object Properties
 *  ---------------------------------------------------------------------------------------
 */

/**
 *  A Boolean value that indicates whether the port is open. (read-only)
 */
@property (readonly, getter = isOpen) BOOL open;

/**
 *  An string representation of the device path for the serial port represented by the receiver. (read-only)
 */
@property (copy, readonly) NSString *path;

/**
 *  The IOKit port object for the serial port device represented by the receiver. (read-only)
 */
@property (readonly) io_object_t IOKitDevice;

/**
 *  The name of the serial port. 
 *  
 *  Can be presented to the user, e.g. in a serial port selection pop up menu.
 */
@property (copy, readonly) NSString *name;

/** ---------------------------------------------------------------------------------------
 * @name Configuring the Serial Port
 *  ---------------------------------------------------------------------------------------
 */

/**
 *  The baud rate for the port.
 *
 *  This value should be one of the values defined in termios.h:
 *
 *	- 0
 *	- 50
 *	- 75
 *	- 110
 *	- 134
 *	- 150
 *	- 200
 *	- 300
 *	- 600
 *	- 1200
 *	- 1800
 *	- 2400
 *	- 4800
 *	- 9600
 *	- 19200
 *	- 38400
 *	- 7200
 *	- 14400
 *	- 28800
 *	- 57600
 *	- 76800
 *	- 115200
 *	- 230400
 *	- 19200
 *	- 38400
 */
@property (nonatomic, copy) NSNumber *baudRate;

/**
 *  The number of stop bits. Values other than 1 or 2 are invalid.
 */
@property (nonatomic) NSUInteger numberOfStopBits;

/**
 *
 */
@property (nonatomic) BOOL shouldEchoReceivedData;

/**
 *  The parity setting for the port. Possible values are:
 *  
 *  - ORSSerialPortParityNone
 *  - ORSSerialPortParityOdd
 *  - ORSSerialPortParityEven
 */
@property (nonatomic) ORSSerialPortParity parity;

/**
 *  A Boolean value indicating whether the serial port uses RTS/CTS Flow Control.
 */
@property (nonatomic) BOOL usesRTSCTSFlowControl;

/**
 *  A Boolean value indicating whether the serial port uses DTR/DSR Flow Control.
 */
@property (nonatomic) BOOL usesDTRDSRFlowControl;

/**
 *  A Boolean value indicating whether the serial port uses DCD Flow Control.
 */
@property (nonatomic) BOOL usesDCDOutputFlowControl;

/** ---------------------------------------------------------------------------------------
 * @name Other Port Pins
 *  ---------------------------------------------------------------------------------------
 */

/**
 *  The state of the serial port's RTS pin.
 *
 *  - YES means 1 or high state.
 *  - NO means 0 or low state.
 *
 *  This property is observable using Key Value Observing.
 */
@property (nonatomic) BOOL RTS;

/**
 *  The state of the serial port's DTR pin.
 *
 *  - YES means 1 or high state.
 *  - NO means 0 or low state.
 *
 *  This property is observable using Key Value Observing.
 */
@property (nonatomic) BOOL DTR;

/**
 *  The state of the serial port's CTS pin.
 *
 *  - YES means 1 or high state.
 *  - NO means 0 or low state.
 *
 *  This property is observable using Key Value Observing.
 */
@property (nonatomic, readonly) BOOL CTS;

/**
 *  The state of the serial port's DSR pin. (read-only)
 *
 *  - YES means 1 or high state.
 *  - NO means 0 or low state.
 *
 *  This property is observable using Key Value Observing.
 */
@property (nonatomic, readonly) BOOL DSR;

/**
 *  The state of the serial port's DCD pin. (read-only)
 *
 *  - YES means 1 or high state.
 *  - NO means 0 or low state.
 *
 *  This property is observable using Key Value Observing.
 */
@property (nonatomic, readonly) BOOL DCD;

@end

/**
 *  The ORSSerialPortDelegate protocol defines methods to be implemented
 *  by the delegate of an `ORSSerialPort` object.
 *
 *  *Note*: All `ORSSerialPortDelegate` methods are always called on the main queue.
 *  If you need to handle them on a background queue, you must dispatch your handling
 *  to a background queue in your implementation of the delegate method.
 */

@protocol ORSSerialPortDelegate

@required

/**
 *  Called when new data is received by the serial port from an external source.
 *
 *  @param serialPort The `ORSSerialPort` instance representing the port that received `data`.
 *  @param data       An `NSData` instance containing the data received.
 */
- (void)serialPort:(ORSSerialPort *)serialPort didReceiveData:(NSData *)data;

/**
 *  Called when a serial port is removed from the system, e.g. the user unplugs
 *  the USB to serial adapter for the port.
 *
 *	In this method, you should discard any strong references you have maintained for the
 *  passed in `serialPort` object. The behavior of `ORSSerialPort` instances whose underlying
 *  serial port has been removed from the system is undefined.
 *
 *  @param serialPort The `ORSSerialPort` instance representing the port that was removed.
 */
- (void)serialPortWasRemovedFromSystem:(ORSSerialPort *)serialPort;

@optional

/**
 *  Called when an error occurs during an operation involving a serial port.
 *
 *	This method is always used to report errors. No `ORSSerialPort` methods
 *  take a passed in `NSError **` reference because errors may occur asynchonously,
 *  after a method has returned.
 *
 *	Currently, errors reported using this method are always in the `NSPOSIXErrorDomain`,
 *  and a list of error codes can be found in the system header errno.h.
 *
 *  The error object's userInfo dictionary contains the following keys:
 *
 *	- NSLocalizedDescriptionKey - An error message string.
 *	- NSFilePathErrorKey - The device path to the serial port. Same as `[serialPort path]`.
 *
 *  @param serialPort The `ORSSerialPort` instance for the port
 *  @param error      An `NSError` object containing information about the error.
 */
- (void)serialPort:(ORSSerialPort *)serialPort didEncounterError:(NSError *)error;

/**
 *  Called when a serial port is successfully opened.
 *
 *  @param serialPort The `ORSSerialPort` instance representing the port that was opened.
 */
- (void)serialPortWasOpened:(ORSSerialPort *)serialPort;

/**
 *  Called when a serial port was closed (e.g. because `-close`) was called.
 *
 *  @param serialPort The `ORSSerialPort` instance representing the port that was closed.
 */
- (void)serialPortWasClosed:(ORSSerialPort *)serialPort;

@end

ORSSerialPort.m


#if !__has_feature(objc_arc)
#error ORSSerialPort.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for ORSSerialPort.m in the Build Phases for this target
#endif

#if OS_OBJECT_USE_OBJC && __has_feature(objc_arc)
#define ORS_GCD_RELEASE(x)
#define ORS_GCD_RETAIN(x)
#else
#define ORS_GCD_RELEASE(x) dispatch_release(x)
#define ORS_GCD_RETAIN(x) dispatch_retain(x)
#endif

#import "ORSSerialPort.h"
#import <IOKit/serial/IOSerialKeys.h>
#import <IOKit/serial/ioss.h>
#import <sys/param.h>
#import <sys/filio.h>
#import <sys/ioctl.h>

#ifdef LOG_SERIAL_PORT_ERRORS
#define LOG_SERIAL_PORT_ERROR(fmt, ...) NSLog(fmt, ## __VA_ARGS__)
#else
#define LOG_SERIAL_PORT_ERROR(fmt, ...)
#endif

static __strong NSMutableArray *allSerialPorts;

@interface ORSSerialPort ()
{
	struct termios originalPortAttributes;
}

@property (copy, readwrite) NSString *path;
@property (readwrite) io_object_t IOKitDevice;
@property (copy, readwrite) NSString *name;

@property (strong) NSMutableData *writeBuffer;
@property int fileDescriptor;

@property (nonatomic, readwrite) BOOL CTS;
@property (nonatomic, readwrite) BOOL DSR;
@property (nonatomic, readwrite) BOOL DCD;

#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong) dispatch_source_t pinPollTimer;
#else
@property (nonatomic) dispatch_source_t pinPollTimer;
#endif

@end

@implementation ORSSerialPort

+ (void)initialize
{
	static dispatch_once_t once;
	dispatch_once(&once, ^{
		allSerialPorts = [[NSMutableArray alloc] init];
	});
}

+ (void)addSerialPort:(ORSSerialPort *)port;
{
	[allSerialPorts addObject:[NSValue valueWithNonretainedObject:port]];
}

+ (void)removeSerialPort:(ORSSerialPort *)port;
{
	NSValue *valueToRemove = nil;
	for (NSValue *value in allSerialPorts)
	{
		if ([value nonretainedObjectValue] == port)
		{
			valueToRemove = value;
			break;
		}
	}
	if (valueToRemove) [allSerialPorts removeObject:valueToRemove];
}

+ (ORSSerialPort *)existingPortWithPath:(NSString *)path;
{
	ORSSerialPort *existingPort = nil;
	for (NSValue *value in allSerialPorts)
	{
		ORSSerialPort *port = [value nonretainedObjectValue];
		if ([port.path isEqualToString:path])
		{
			existingPort = port;
			break;
		}
	}
	
	return existingPort;
}

+ (ORSSerialPort *)serialPortWithPath:(NSString *)devicePath
{
 	return [[self alloc] initWithPath:devicePath];
}

+ (ORSSerialPort *)serialPortWithDevice:(io_object_t)device;
{
	return [[self alloc] initWithDevice:device];
}

- (id)initWithPath:(NSString *)devicePath
{
 	io_object_t device = [[self class] deviceFromBSDPath:devicePath];
 	if (device == 0)
 	{
 		self = nil;
 		return self;
 	}
 	
 	return [self initWithDevice:device];
}

- (id)initWithDevice:(io_object_t)device;
{
	NSAssert(device != 0, @"%s requires non-zero device argument.", __PRETTY_FUNCTION__);
	
	NSString *bsdPath = [[self class] bsdCalloutPathFromDevice:device];
	ORSSerialPort *existingPort = [[self class] existingPortWithPath:bsdPath];
	
	if (existingPort != nil)
	{
		self = nil;
		return existingPort;
	}
	
	self = [super init];
	
	if (self != nil)
	{
		self.ioKitDevice = device;
		self.path = bsdPath;
		self.name = [[self class] modemNameFromDevice:device];
		self.writeBuffer = [NSMutableData data];
        self.baudRate = @B9600;//@B19200;
		self.numberOfStopBits = 1;
		self.parity = ORSSerialPortParityNone;
		self.shouldEchoReceivedData = NO;
		self.usesRTSCTSFlowControl = NO;
		self.usesDTRDSRFlowControl = NO;
		self.usesDCDOutputFlowControl = NO;
		self.RTS = NO;
		self.DTR = NO;
	}
	
	[[self class] addSerialPort:self];
	
	return self;
}

- (id)init
{
    NSAssert(0, @"ORSSerialPort must be init'd using -initWithPath:");
	return nil;
}

- (void)dealloc
{
	[[self class] removeSerialPort:self];
	self.IOKitDevice = 0;
	
	if (_pinPollTimer) {
		dispatch_source_cancel(_pinPollTimer);
		ORS_GCD_RELEASE(_pinPollTimer);
	}
}

- (NSString *)description
{
	return self.name;
	//	io_object_t device = [[self class] deviceFromBSDPath:self.path];
	//	return [NSString stringWithFormat:@"BSD Path:%@, base name:%@, modem name:%@, suffix:%@, service type:%@", [[self class] bsdCalloutPathFromDevice:device], [[self class] baseNameFromDevice:device], [[self class] modemNameFromDevice:device], [[self class] suffixFromDevice:device], [[self class] serviceTypeFromDevice:device]];
}

- (NSUInteger)hash
{
	return [self.path hash];
}

- (BOOL)isEqual:(id)object
{
	if (![object isKindOfClass:[self class]]) return NO;
	if (![object respondsToSelector:@selector(path)]) return NO;
	
	return [self.path isEqual:[object path]];
}

#pragma mark - Public Methods

- (BOOL)open;
{
	if (self.isOpen)
        return NO;
	
	dispatch_queue_t mainQueue = dispatch_get_main_queue();
	
	int descriptor=0;
	descriptor = open([self.path cStringUsingEncoding:NSASCIIStringEncoding], O_RDWR | O_NOCTTY | O_EXLOCK | O_NONBLOCK);
	if (descriptor < 1)
	{
		// Error
		[self notifyDelegateOfPosixError];
		return NO;
	}
	
	// Now that the device is open, clear the O_NONBLOCK flag so subsequent I/O will block.
	// See fcntl(2) ("man 2 fcntl") for details.
	
	if (fcntl(descriptor, F_SETFL, 0) == -1)
	{
		LOG_SERIAL_PORT_ERROR(@"Error clearing O_NONBLOCK %@ - %s(%d).\n", self.path, strerror(errno), errno);
        //return NO;
	}
	
	self.fileDescriptor = descriptor;
	
	// Port opened successfully, set options
	tcgetattr(descriptor, &originalPortAttributes); // Get original options so they can be reset later
	[self setPortOptions];
	
	// Get status of RTS and DTR lines
	int modemLines=0;
	if (ioctl(self.fileDescriptor, TIOCMGET, &modemLines) < 0)
	{
		LOG_SERIAL_PORT_ERROR(@"Error reading modem lines status");
		[self notifyDelegateOfPosixError];
	}
	
	BOOL desiredRTS = self.RTS;
	BOOL desiredDTR = self.DTR;
	self.RTS = modemLines & TIOCM_RTS;
	self.DTR = modemLines & TIOCM_DTR;
	self.RTS = desiredRTS;
	self.DTR = desiredDTR;
	
    if ([(id)self.delegate respondsToSelector:@selector(serialPortWasOpened:)])
    {
        dispatch_async(mainQueue, ^{
			[self.delegate serialPortWasOpened:self];
        });
    }
	
	// Start a read poller in the background
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
		
		int localPortFD = self.fileDescriptor;
		struct timeval timeout;
		int result=0;
		
		while (self.isOpen)
		{
			fd_set localReadFDSet;
			FD_ZERO(&localReadFDSet);
			FD_SET(localPortFD, &localReadFDSet);
			
			timeout.tv_sec = 0;
			timeout.tv_usec = 100000; // Check to see if port closed every 100ms
			
			result = select(localPortFD+1, &localReadFDSet, NULL, NULL, &timeout);
			if (!self.isOpen)
                break; // Port closed while select call was waiting
			if (result < 0)
			{
				[self notifyDelegateOfPosixError];
				continue;
			}
			
			if (result == 0 || !FD_ISSET(localPortFD, &localReadFDSet))
                continue;
			
			// Data is available
			char buf[1024];
			long lengthRead = read(localPortFD, buf, sizeof(buf));
			if (lengthRead>0)
			{
				NSData *readData = [NSData dataWithBytes:buf length:lengthRead];
				if (readData != nil)
                    [self receiveData:readData];
			}
		}
	});
	
	// Start another poller to check status of CTS and DSR
	dispatch_queue_t pollQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
	dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, pollQueue);
	dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), 10*NSEC_PER_MSEC, 5*NSEC_PER_MSEC);
	dispatch_source_set_event_handler(timer, ^{
		if (!self.isOpen) {
			dispatch_async(pollQueue, ^{ dispatch_source_cancel(timer); });
			return;
		}
		
		int32_t modemLines=0;
		int result = ioctl(self.fileDescriptor, TIOCMGET, &modemLines);
		if (result < 0)
		{
			[self notifyDelegateOfPosixErrorWaitingUntilDone:(errno == ENXIO)];
			if (errno == ENXIO)
			{
				[self cleanupAfterSystemRemoval];
			}
			return;
		}
		
		BOOL CTSPin = (modemLines & TIOCM_CTS) != 0;
		BOOL DSRPin = (modemLines & TIOCM_DSR) != 0;
		BOOL DCDPin = (modemLines & TIOCM_CAR) != 0;
		
		if (CTSPin != self.CTS)
			dispatch_sync(mainQueue, ^{self.CTS = CTSPin;});
		if (DSRPin != self.DSR)
			dispatch_sync(mainQueue, ^{self.DSR = DSRPin;});
		if (DCDPin != self.DCD)
			dispatch_sync(mainQueue, ^{self.DCD = DCDPin;});
	});
	self.pinPollTimer = timer;
	dispatch_resume(self.pinPollTimer);
	ORS_GCD_RELEASE(timer);
    return YES;
}

- (BOOL)close;
{
	if (!self.isOpen)
        return YES;
	
	[self.writeBuffer replaceBytesInRange:NSMakeRange(0, [self.writeBuffer length]) withBytes:NULL length:0];
	
	self.pinPollTimer = nil; // Stop polling CTS/DSR/DCD pins
	
	// The next tcsetattr() call can fail if the port is waiting to send data. This is likely to happen
	// e.g. if flow control is on and the CTS line is low. So, turn off flow control before proceeding
	struct termios options;
	tcgetattr(self.fileDescriptor, &options);
	options.c_cflag &= ~CRTSCTS; // RTS/CTS Flow Control
	options.c_cflag &= ~(CDTR_IFLOW | CDSR_OFLOW); // DTR/DSR Flow Control
	options.c_cflag &= ~CCAR_OFLOW; // DCD Flow Control
	tcsetattr(self.fileDescriptor, TCSANOW, &options);
	
	// Set port back the way it was before we used it
	tcsetattr(self.fileDescriptor, TCSADRAIN, &originalPortAttributes);
	
	int localFD = self.fileDescriptor;
	self.fileDescriptor = 0; // So other threads know that the port should be closed and can stop I/O operations
	
	if (close(localFD))
	{
		self.fileDescriptor = localFD;
		LOG_SERIAL_PORT_ERROR(@"Error closing serial port with file descriptor %i:%i", self.fileDescriptor, errno);
		[self notifyDelegateOfPosixError];
		return NO;
	}
	
	if ([(id)self.delegate respondsToSelector:@selector(serialPortWasClosed:)])
	{
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.delegate serialPortWasClosed:self];
        });
	}
	return YES;
}

- (void)cleanup;
{
	NSLog(@"Cleanup is deprecated and was never intended to be called publicly. You should update your code to avoid calling this method.");
	[self cleanupAfterSystemRemoval];
}

- (void)cleanupAfterSystemRemoval
{
	if ([(id)self.delegate respondsToSelector:@selector(serialPortWasRemovedFromSystem:)])
	{
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.delegate serialPortWasRemovedFromSystem:self];
			[self close];
        });
	}
	else
	{
		[self close];
	}
}

- (BOOL)sendData:(NSData *)data;
{
	if (!self.isOpen)
        return NO;
	
	[self.writeBuffer appendData:data];
	
	if ([self.writeBuffer length] < 1) return YES;
	
	long numBytesWritten = write(self.fileDescriptor, [self.writeBuffer bytes], [self.writeBuffer length]);
	if (numBytesWritten < 0)
	{
		LOG_SERIAL_PORT_ERROR(@"Error writing to serial port:%d", errno);
		[self notifyDelegateOfPosixError];
		return NO;
	}
	if (numBytesWritten > 0) [self.writeBuffer replaceBytesInRange:NSMakeRange(0, numBytesWritten) withBytes:NULL length:0];
	
	return YES;
}

#pragma mark - Private Methods

#pragma mark Port Read/Write

- (void)receiveData:(NSData *)data;
{
	if ([(id)self.delegate respondsToSelector:@selector(serialPort:didReceiveData:)])
	{
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.delegate serialPort:self didReceiveData:data];
        });
	}
}

#pragma mark Port Propeties Methods

- (void)setPortOptions;
{
	if ([self fileDescriptor] < 1) return;
	
	struct termios options;
	
	tcgetattr(self.fileDescriptor, &options);
	
	cfmakeraw(&options);
	options.c_cc[VMIN] = 1; // Wait for at least 1 character before returning
	options.c_cc[VTIME] = 2; // Wait 200 milliseconds between bytes before returning from read
	
	// Set 8 data bits
	options.c_cflag &= ~CSIZE;
	options.c_cflag |= CS8;
	
	// Set parity
	switch (self.parity) {
		case ORSSerialPortParityNone:
			options.c_cflag &= ~PARENB;
			break;
		case ORSSerialPortParityEven:
			options.c_cflag |= PARENB;
			options.c_cflag &= ~PARODD;
			break;
		case ORSSerialPortParityOdd:
			options.c_cflag |= PARENB;
			options.c_cflag |= PARODD;
			break;
		default:
			break;
	}
	
	options.c_cflag = [self numberOfStopBits] > 1 ? options.c_cflag | CSTOPB : options.c_cflag & ~CSTOPB; // number of stop bits
	options.c_lflag = [self shouldEchoReceivedData] ? options.c_lflag | ECHO : options.c_lflag & ~ECHO; // echo
	options.c_cflag = [self usesRTSCTSFlowControl] ? options.c_cflag | CRTSCTS : options.c_cflag & ~CRTSCTS; // RTS/CTS Flow Control
	options.c_cflag = [self usesDTRDSRFlowControl] ? options.c_cflag | (CDTR_IFLOW | CDSR_OFLOW) : options.c_cflag & ~(CDTR_IFLOW | CDSR_OFLOW); // DTR/DSR Flow Control
	options.c_cflag = [self usesDCDOutputFlowControl] ? options.c_cflag | CCAR_OFLOW : options.c_cflag & ~CCAR_OFLOW; // DCD Flow Control
	
	options.c_cflag |= HUPCL; // Turn on hangup on close
	options.c_cflag |= CLOCAL; // Set local mode on
	options.c_cflag |= CREAD; // Enable receiver
	options.c_lflag &= ~(ICANON /*| ECHO*/ | ISIG); // Turn off canonical mode and signals
	
	// Set baud rate
	cfsetspeed(&options, [[self baudRate] unsignedLongValue]);
	
	// TODO: Call delegate error handling method if this fails
	int result = tcsetattr(self.fileDescriptor, TCSANOW, &options);
	if (result != 0) NSLog(@"Unable to set options on %@: %i", self, result);
}

+ (io_object_t)deviceFromBSDPath:(NSString *)bsdPath;
{
	if ([bsdPath length] < 1) return 0;
	
	CFMutableDictionaryRef matchingDict = NULL;
	
	matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
	CFRetain(matchingDict); // Need to use it twice
	
	CFDictionaryAddValue(matchingDict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes));
	
	io_iterator_t portIterator = 0;
	kern_return_t err = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &portIterator);
	CFRelease(matchingDict);
	if (err) return 0;
	
	io_object_t eachPort = 0;
	io_object_t result = 0;
	while ((eachPort = IOIteratorNext(portIterator)))
	{
		NSString *calloutPath = [self bsdCalloutPathFromDevice:eachPort];
		NSString *dialinPath = [self bsdDialinPathFromDevice:eachPort];
		if ([bsdPath isEqualToString:calloutPath] ||
			[bsdPath isEqualToString:dialinPath])
		{
			result = eachPort;
			break;
		}
		IOObjectRelease(eachPort);
	}
	IOObjectRelease(portIterator);
	
	return result;
}

+ (NSString *)stringPropertyOf:(io_object_t)aDevice forIOSerialKey:(NSString *)key;
{
	CFStringRef string = (CFStringRef)IORegistryEntryCreateCFProperty(aDevice,(__bridge CFStringRef)key,kCFAllocatorDefault,0);
	return (__bridge_transfer NSString *)string;
}

+ (NSString *)bsdCalloutPathFromDevice:(io_object_t)aDevice;
{
	return [self stringPropertyOf:aDevice forIOSerialKey:(NSString*)CFSTR(kIOCalloutDeviceKey)];
}

+ (NSString *)bsdDialinPathFromDevice:(io_object_t)aDevice;
{
	return [self stringPropertyOf:aDevice forIOSerialKey:(NSString*)CFSTR(kIODialinDeviceKey)];
}

+ (NSString *)baseNameFromDevice:(io_object_t)aDevice;
{
	return [self stringPropertyOf:aDevice forIOSerialKey:(NSString*)CFSTR(kIOTTYBaseNameKey)];
}

+ (NSString *)serviceTypeFromDevice:(io_object_t)aDevice;
{
	return [self stringPropertyOf:aDevice forIOSerialKey:(NSString*)CFSTR(kIOSerialBSDTypeKey)];
}

+ (NSString *)modemNameFromDevice:(io_object_t)aDevice;
{
	return [self stringPropertyOf:aDevice forIOSerialKey:(NSString*)CFSTR(kIOTTYDeviceKey)];
}

+ (NSString *)suffixFromDevice:(io_object_t)aDevice;
{
	return [self stringPropertyOf:aDevice forIOSerialKey:(NSString*)CFSTR(kIOTTYSuffixKey)];
}

#pragma mark Helper Methods

- (void)notifyDelegateOfPosixError
{
	[self notifyDelegateOfPosixErrorWaitingUntilDone:NO];
}

- (void)notifyDelegateOfPosixErrorWaitingUntilDone:(BOOL)shouldWait;
{
	if (![(id)self.delegate respondsToSelector:@selector(serialPort:didEncounterError:)]) return;
	
	NSDictionary *errDict = @{NSLocalizedDescriptionKey: @(strerror(errno)),
							  NSFilePathErrorKey: self.path};
	NSError *error = [NSError errorWithDomain:NSPOSIXErrorDomain
										 code:errno
									 userInfo:errDict];
	
	void (^notifyBlock)(void) = ^{
		[self.delegate serialPort:self didEncounterError:error];
	};
	
	if ([NSThread isMainThread]) {
		notifyBlock();
	} else if (shouldWait) {
		dispatch_sync(dispatch_get_main_queue(), notifyBlock);
	} else {
		dispatch_async(dispatch_get_main_queue(), notifyBlock);
	}
}

#pragma mark - Properties

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
	NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
	if ([key isEqualToString:@"isOpen"])
	{
		keyPaths = [keyPaths setByAddingObject:@"fileDescriptor"];
	}
	
	return keyPaths;
}

@synthesize delegate = _delegate;

#pragma mark Port Properties

- (BOOL)isOpen
{
    return self.fileDescriptor != 0;
}

@synthesize path = _path;

@synthesize IOKitDevice = _IOKitDevice;
- (void)setIoKitDevice:(io_object_t)device
{
	if (device != _IOKitDevice) {
		if (_IOKitDevice) IOObjectRelease(_IOKitDevice);
		_IOKitDevice = device;
		if (_IOKitDevice) IOObjectRetain(_IOKitDevice);
	}
}

@synthesize name = _name;

@synthesize baudRate = _baudRate;
- (void)setBaudRate:(NSNumber *)rate
{
	if (rate != _baudRate)
	{
		_baudRate = [rate copy];
		
		[self setPortOptions];
	}
}

@synthesize numberOfStopBits = _numberOfStopBits;
- (void)setNumberOfStopBits:(NSUInteger)num
{
	if (num != _numberOfStopBits)
	{
		_numberOfStopBits = num;
		[self setPortOptions];
	}
}

@synthesize shouldEchoReceivedData = _shouldEchoReceivedData;
- (void)setShouldEchoReceivedData:(BOOL)flag
{
	if (flag != _shouldEchoReceivedData)
	{
		_shouldEchoReceivedData = flag;
		[self setPortOptions];
	}
}

@synthesize parity = _parity;
- (void)setParity:(ORSSerialPortParity)aParity
{
	if (aParity != _parity)
	{
		if (aParity != ORSSerialPortParityNone &&
			aParity != ORSSerialPortParityOdd &&
			aParity != ORSSerialPortParityEven)
		{
			aParity = ORSSerialPortParityNone;
		}
		
		_parity = aParity;
		[self setPortOptions];
	}
}

@synthesize usesRTSCTSFlowControl = _usesRTSCTSFlowControl;
- (void)setUsesRTSCTSFlowControl:(BOOL)flag
{
	if (flag != _usesRTSCTSFlowControl)
	{
		// Turning flow control one while the port is open doesn't seem to work right,
		// at least with some drivers, so close it then reopen it if needed
		BOOL shouldReopen = self.isOpen;
		[self close];
		
		_usesRTSCTSFlowControl = flag;
		
		[self setPortOptions];
		if (shouldReopen)
            [self open];
	}
}

@synthesize usesDTRDSRFlowControl = _usesDTRDSRFlowControl;
- (void)setUsesDTRDSRFlowControl:(BOOL)flag
{
	if (flag != _usesDTRDSRFlowControl)
	{
		// Turning flow control one while the port is open doesn't seem to work right,
		// at least with some drivers, so close it then reopen it if needed
		BOOL shouldReopen = self.isOpen;
		[self close];
		
		_usesDTRDSRFlowControl = flag;
		[self setPortOptions];
		if (shouldReopen)
            [self open];
	}
}

@synthesize usesDCDOutputFlowControl = _usesDCDOutputFlowControl;
- (void)setUsesDCDOutputFlowControl:(BOOL)flag
{
	if (flag != _usesDCDOutputFlowControl)
	{
		// Turning flow control one while the port is open doesn't seem to work right,
		// at least with some drivers, so close it then reopen it if needed
		BOOL shouldReopen = self.isOpen;
		[self close];
		
		_usesDCDOutputFlowControl = flag;
		
		[self setPortOptions];
		if (shouldReopen) [self open];
	}
}

@synthesize RTS = _RTS;
- (void)setRTS:(BOOL)flag
{
	if (flag != _RTS)
	{
		_RTS = flag;
		
		if (![self isOpen]) return;
		
		int bits;
		ioctl( self.fileDescriptor, TIOCMGET, &bits ) ;
		bits = _RTS ? bits | TIOCM_RTS : bits & ~TIOCM_RTS;
		if (ioctl( self.fileDescriptor, TIOCMSET, &bits ) < 0)
		{
			LOG_SERIAL_PORT_ERROR(@"Error in %s", __PRETTY_FUNCTION__);
			[self notifyDelegateOfPosixError];
		}
	}
}

@synthesize DTR = _DTR;
- (void)setDTR:(BOOL)flag
{
	if (flag != _DTR)
	{
		_DTR = flag;
		
		if (![self isOpen]) return;
		
		int bits;
		ioctl( self.fileDescriptor, TIOCMGET, &bits ) ;
		bits = _DTR ? bits | TIOCM_DTR : bits & ~TIOCM_DTR;
		if (ioctl( self.fileDescriptor, TIOCMSET, &bits ) < 0)
		{
			LOG_SERIAL_PORT_ERROR(@"Error in %s", __PRETTY_FUNCTION__);
			[self notifyDelegateOfPosixError];
		}
	}
}

@synthesize CTS = _CTS;
@synthesize DSR = _DSR;
@synthesize DCD = _DCD;

#pragma mark Private Properties

@synthesize writeBuffer = _writeBuffer;
@synthesize fileDescriptor = _fileDescriptor;
@synthesize pinPollTimer = _pinPollTimer;
- (void)setPinPollTimer:(dispatch_source_t)timer
{
	if (timer != _pinPollTimer)
	{
		if (_pinPollTimer)
		{
			dispatch_source_cancel(_pinPollTimer);
			ORS_GCD_RELEASE(_pinPollTimer);
		}
		
        if (timer) {
            ORS_GCD_RETAIN(timer);
        }
		_pinPollTimer = timer;
	}
}

@end


ORSSerialPortManager.h


#import <Foundation/Foundation.h>

/// Posted when a serial port is connected to the system
extern NSString * const ORSSerialPortsWereConnectedNotification;

/// Posted when a serial port is disconnected from the system
extern NSString * const ORSSerialPortsWereDisconnectedNotification;

/// Key for connected port in ORSSerialPortWasConnectedNotification userInfo dictionary
extern NSString * const ORSConnectedSerialPortsKey;
/// Key for disconnected port in ORSSerialPortWasDisconnectedNotification userInfo dictionary
extern NSString * const ORSDisconnectedSerialPortsKey;

/**
 *  `ORSSerialPortManager` is a singleton class (one instance per
 *  application) that can be used to get a list of available serial ports.
 *  It will also handle closing open serial ports when the Mac goes to
 *  sleep, and reopening them automatically on wake. This prevents problems
 *  I've seen with serial port drivers that can hang if the port is left
 *  open when putting the machine to sleep. Note that using
 *  `ORSSerialPortManager` is optional. It provides some nice functionality,
 *  but only `ORSSerialPort` is necessary to simply send and received data.
 *
 *  Using ORSSerialPortManager
 *  --------------------------
 *
 *  To get the shared serial port
 *  manager:
 *
 *      ORSSerialPortManager *portManager = [ORSSerialPortManager sharedSerialPortManager];
 *
 *  To get a list of available ports:
 *
 *      NSArray *availablePorts = portManager.availablePorts;
 *
 *  Notifications
 *  -------------
 *
 *  `ORSSerialPort` posts notifications when a port is added to or removed from the system.
 *  `ORSSerialPortsWereConnectedNotification` is posted when one or more ports
 *  are added to the system. `ORSSerialPortsWereDisconnectedNotification` is posted when
 *  one ore more ports are removed from the system. The user info dictionary for each
 *  notification contains the list of ports added or removed. The keys to access these array
 *  are `ORSConnectedSerialPortsKey`, and `ORSDisconnectedSerialPortsKey` respectively.
 *
 *  KVO Compliance
 *  --------------
 *
 *  `ORSSerialPortManager` is Key-Value Observing (KVO) compliant for its
 *  `availablePorts` property. This means that you can observe
 *  `availablePorts` to be notified when ports are added to or removed from
 *  the system. This also means that you can easily bind UI elements to the
 *  serial port manager's `availablePorts` property using Cocoa-bindings.
 *  This makes it easy to create a popup menu that displays available serial
 *  ports and updates automatically, for example.
 *
 *  Close-On-Sleep
 *  --------------
 *
 *  `ORSSerialPortManager`'s close-on-sleep, reopen-on-wake functionality is
 *  automatic. The only thing necessary to enable it is to make sure that
 *  the singleton instance of `ORSSerialPortManager` has been created by
 *  calling `+sharedSerialPortManager` at least once. Note that this
 *  behavior is only available in Cocoa apps, and is disabled when
 *  ORSSerialPort is used in a command-line only app.
 */
@interface ORSSerialPortManager : NSObject

/**
 *  Returns the shared (singleton) serial port manager object.
 *
 *  @return The shared serial port manager.
 */
+ (ORSSerialPortManager *)sharedSerialPortManager;

/**
 *  An array containing ORSSerialPort instances representing the
 *  serial ports available on the system. (read-only)
 *  
 *  As explained above, this property is Key Value Observing
 *  compliant, and can be bound to for example an NSPopUpMenu
 *  to easily give the user a way to select an available port 
 *  on the system.
 */
@property (nonatomic, copy, readonly) NSArray *availablePorts;

@end



ORSSerialPortManager.m


#if !__has_feature(objc_arc)
	#error ORSSerialPortManager.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for ORSSerialPortManager.m in the Build Phases for this target
#endif

#import "ORSSerialPortManager.h"
#import "ORSSerialPort.h"

#import <IOKit/IOKitLib.h>
#import <IOKit/serial/IOSerialKeys.h>

#ifdef LOG_SERIAL_PORT_ERRORS
	#define LOG_SERIAL_PORT_ERROR(fmt, ...) NSLog(fmt, ##__VA_ARGS__)
#else
	#define LOG_SERIAL_PORT_ERROR(fmt, ...)
#endif

NSString * const ORSSerialPortsWereConnectedNotification = @"ORSSerialPortWasConnectedNotification";
NSString * const ORSSerialPortsWereDisconnectedNotification = @"ORSSerialPortWasDisconnectedNotification";

NSString * const ORSConnectedSerialPortsKey = @"ORSConnectedSerialPortsKey";
NSString * const ORSDisconnectedSerialPortsKey = @"ORSDisconnectedSerialPortsKey";

void ORSSerialPortManagerPortsPublishedNotificationCallback(void *refCon, io_iterator_t iterator);
void ORSSerialPortManagerPortsTerminatedNotificationCallback(void *refCon, io_iterator_t iterator);

@interface ORSSerialPortManager ()

@property (nonatomic, copy, readwrite) NSArray *availablePorts;
@property (nonatomic, strong) NSMutableArray *portsToReopenAfterSleep;

@property (nonatomic) io_iterator_t portPublishedNotificationIterator;
@property (nonatomic) io_iterator_t portTerminatedNotificationIterator;

@end

static ORSSerialPortManager *sharedInstance = nil;

@implementation ORSSerialPortManager
{
	NSMutableArray *_availablePorts;
}

#pragma mark - Singleton Methods

- (id)init
{
	if (self == sharedInstance)
        return sharedInstance; // Already initialized
	
	self = [super init];
	if (self != nil)
	{
		self.portsToReopenAfterSleep = [NSMutableArray array];
		
		[self retrieveAvailablePortsAndRegisterForChangeNotifications];
		[self registerForNotifications];
	}
	return self;
}

+ (ORSSerialPortManager *)sharedSerialPortManager;
{
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		if (sharedInstance == nil)
            sharedInstance = [(ORSSerialPortManager *)[super allocWithZone:NULL] init];
	});
	
	return sharedInstance;
}

+ (id)allocWithZone:(NSZone *)zone
{
	return [self sharedSerialPortManager];
}

- (id)copyWithZone:(NSZone *)zone
{
	return self;
}

- (void)dealloc
{
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	[nc removeObserver:self];
	
	// Stop IOKit notifications for ports being added/removed
	IOObjectRelease(_portPublishedNotificationIterator);
	_portPublishedNotificationIterator = 0;
	IOObjectRelease(_portTerminatedNotificationIterator);
	_portTerminatedNotificationIterator = 0;
}

- (void)registerForNotifications
{
	// register for notifications (only if AppKit is available)
	void (^terminationBlock)(void) = ^{
		for (ORSSerialPort *eachPort in self.availablePorts) [eachPort cleanupAfterSystemRemoval];
		self.availablePorts = nil;
	};
	
#ifdef NSAppKitVersionNumber10_0
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	[nc addObserverForName:NSApplicationWillTerminateNotification object:nil queue:nil usingBlock:^(NSNotification *notification){
        // For some unknown reason, this notification fires twice,
        // doesn't cause a problem right now, but be aware
		terminationBlock();
	}];
	
	[nc addObserver:self selector:@selector(systemWillSleep:) name:NSWorkspaceWillSleepNotification object:NULL];
	[nc addObserver:self selector:@selector(systemDidWake:) name:NSWorkspaceDidWakeNotification object:NULL];
#else
	// If AppKit isn't available, as in a Foundation command-line tool, cleanup upon exit. Sleep/wake
	// notifications don't seem to be available without NSWorkspace.
	int result = atexit_b(terminationBlock);
	if (result) NSLog(@"ORSSerialPort was unable to register its termination handler for serial port cleanup: %i", errno);
#endif
}

#pragma mark - Public Methods

#pragma mark -
#pragma Sleep/Wake Management

- (void)systemWillSleep:(NSNotification *)notification;
{
	NSArray *ports = self.availablePorts;
	for (ORSSerialPort *port in ports)
	{
		if (port.isOpen)
		{
			if ([port close])
                [self.portsToReopenAfterSleep addObject:port];
		}
	}
}

- (void)systemDidWake:(NSNotification *)notification;
{
	NSArray *portsToReopen = [self.portsToReopenAfterSleep copy];
	for (ORSSerialPort *port in portsToReopen)
	{
		[port open];
		[self.portsToReopenAfterSleep removeObject:port];
	}
}

#pragma mark - Private Methods

- (void)serialPortsWerePublished:(io_iterator_t)iterator;
{
	NSMutableArray *newlyConnectedPorts = [[NSMutableArray alloc] init];
	io_object_t device;
	while ((device = IOIteratorNext(iterator)))
	{
		ORSSerialPort *port = [[ORSSerialPort alloc] initWithDevice:device];
		if (![self.availablePorts containsObject:port]) [newlyConnectedPorts addObject:port];
		IOObjectRelease(device);
	}
	
	[[self mutableArrayValueForKey:@"availablePorts"] addObjectsFromArray:newlyConnectedPorts];
	
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	NSDictionary *userInfo = @{ORSConnectedSerialPortsKey : newlyConnectedPorts};
	[nc postNotificationName:ORSSerialPortsWereConnectedNotification object:self userInfo:userInfo];
}

- (void)serialPortsWereTerminated:(io_iterator_t)iterator;
{
	NSMutableArray *newlyDisconnectedPorts = [[NSMutableArray alloc] init];
	io_object_t device;
	while ((device = IOIteratorNext(iterator)))
	{
		ORSSerialPort *port = [[ORSSerialPort alloc] initWithDevice:device];
		[newlyDisconnectedPorts addObject:port];
		IOObjectRelease(device);
	}
	
	[newlyDisconnectedPorts makeObjectsPerformSelector:@selector(cleanupAfterSystemRemoval)];
	[[self mutableArrayValueForKey:@"availablePorts"] removeObjectsInArray:newlyDisconnectedPorts];
	
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	NSDictionary *userInfo = @{ORSDisconnectedSerialPortsKey : newlyDisconnectedPorts};
	[nc postNotificationName:ORSSerialPortsWereDisconnectedNotification object:self userInfo:userInfo];
}

- (void)retrieveAvailablePortsAndRegisterForChangeNotifications;
{
	IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
	CFRunLoopAddSource(CFRunLoopGetCurrent(),IONotificationPortGetRunLoopSource(notificationPort),kCFRunLoopDefaultMode);
	
	CFMutableDictionaryRef matchingDict = NULL;
	
	matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
	CFRetain(matchingDict); // Need to use it twice
	
	CFDictionaryAddValue(matchingDict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes));
	
	io_iterator_t portIterator = 0;
	kern_return_t result = IOServiceAddMatchingNotification(notificationPort,kIOPublishNotification,matchingDict,ORSSerialPortManagerPortsPublishedNotificationCallback,(__bridge void *)(self),// refCon/contextInfo
		&portIterator);
	if (result){
		LOG_SERIAL_PORT_ERROR(@"Error getting serialPort list:%i", result);
		if (portIterator) IOObjectRelease(portIterator);
		CFRelease(matchingDict); // Above call to IOServiceAddMatchingNotification consumes one reference, but we added a retain for the below call
		return;
	}
	
	self.portPublishedNotificationIterator = portIterator;
	IOObjectRelease(portIterator);
	
	NSMutableArray *ports = [NSMutableArray array];
	io_object_t eachPort;
	while ((eachPort = IOIteratorNext(self.portPublishedNotificationIterator)))
	{
		ORSSerialPort *port = [ORSSerialPort serialPortWithDevice:eachPort];
		if (port) {
            [ports addObject:port];
        }
		IOObjectRelease(eachPort);
	}
	
	self.availablePorts = ports;
	
	// Also register for removal
	IONotificationPortRef terminationNotificationPort = IONotificationPortCreate(kIOMasterPortDefault);
	CFRunLoopAddSource(CFRunLoopGetCurrent(),IONotificationPortGetRunLoopSource(terminationNotificationPort),kCFRunLoopDefaultMode);
	result = IOServiceAddMatchingNotification(terminationNotificationPort,kIOTerminatedNotification,matchingDict,ORSSerialPortManagerPortsTerminatedNotificationCallback,(__bridge void *)(self),// refCon/contextInfo
		&portIterator);
	if (result) {
		LOG_SERIAL_PORT_ERROR(@"Error registering for serial port termination notification:%i.", result);
		if (portIterator) IOObjectRelease(portIterator);
		return;
	}
	
	self.portTerminatedNotificationIterator = portIterator;
	IOObjectRelease(portIterator);
	
	while (IOIteratorNext(self.portTerminatedNotificationIterator)) {
    }; // Run out the iterator or notifications won't start
}

#pragma mark - Properties

@synthesize availablePorts = _availablePorts;

- (void)setAvailablePorts:(NSArray *)ports
{
	if (ports != _availablePorts)
	{
		_availablePorts = [ports mutableCopy];
	}
}

//可用的串口數量
- (NSUInteger)countOfAvailablePorts {
    return [_availablePorts count];
}

//返回某個串口
- (id)objectInAvailablePortsAtIndex:(NSUInteger)index {
    return [_availablePorts objectAtIndex:index];
}

//插入多個串口
- (void)insertAvailablePorts:(NSArray *)array atIndexes:(NSIndexSet *)indexes {
    [_availablePorts insertObjects:array atIndexes:indexes];
}

//插入單個串口
- (void)insertObject:(ORSSerialPort *)object inAvailablePortsAtIndex:(NSUInteger)index {
    [_availablePorts insertObject:object atIndex:index];
}

//移除多個串口
- (void)removeAvailablePortsAtIndexes:(NSIndexSet *)indexes { [_availablePorts removeObjectsAtIndexes:indexes];
}

//移除單個串口
- (void)removeObjectFromAvailablePortsAtIndex:(NSUInteger)index {
    [_availablePorts removeObjectAtIndex:index];
}

@synthesize portsToReopenAfterSleep = _portsToReopenAfterSleep;

@synthesize portPublishedNotificationIterator = _portPublishedNotificationIterator;
- (void)setPortPublishedNotificationIterator:(io_iterator_t)iterator
{
	if (iterator != _portPublishedNotificationIterator)
	{
		if (_portPublishedNotificationIterator)
            IOObjectRelease(_portPublishedNotificationIterator);
		
		_portPublishedNotificationIterator = iterator;
		IOObjectRetain(_portPublishedNotificationIterator);
	}
}

@synthesize portTerminatedNotificationIterator = _portTerminatedNotificationIterator;
- (void)setPortTerminatedNotificationIterator:(io_iterator_t)iterator
{
	if (iterator != _portTerminatedNotificationIterator)
	{
		if (_portTerminatedNotificationIterator) IOObjectRelease(_portTerminatedNotificationIterator);
		
		_portTerminatedNotificationIterator = iterator;
		IOObjectRetain(_portTerminatedNotificationIterator);
	}
}

@end

void ORSSerialPortManagerPortsPublishedNotificationCallback(void *refCon, io_iterator_t iterator)
{
	ORSSerialPortManager *manager = (__bridge ORSSerialPortManager *)refCon;
	if (![manager isKindOfClass:[ORSSerialPortManager class]])
	{
		NSLog(@"Unexpected context object %@ in %s. Context object should be an instance of ORSSerialPortManager", manager, __PRETTY_FUNCTION__);
		return;
	}
	[manager serialPortsWerePublished:iterator];
}

void ORSSerialPortManagerPortsTerminatedNotificationCallback(void *refCon, io_iterator_t iterator)
{
	ORSSerialPortManager *manager = (__bridge ORSSerialPortManager *)refCon;
	if (![manager isKindOfClass:[ORSSerialPortManager class]])
	{
		NSLog(@"Unexpected context object %@ in %s. Context object should be an instance of ORSSerialPortManager", manager, __PRETTY_FUNCTION__);
		return;
	}
	[manager serialPortsWereTerminated:iterator];
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章