hammerspoon/Pods/ORSSerialPort/Source/ORSSerialPort.m

967 lines
28 KiB
Objective-C

//
// ORSSerialPort.m
// ORSSerialPort
//
// Created by Andrew R. Madsen on 08/6/11.
// Copyright (c) 2011-2014 Andrew R. Madsen (andrew@openreelsoftware.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "ORSSerialPort.h"
#import "ORSSerialRequest.h"
#import "ORSSerialBuffer.h"
#import <IOKit/serial/IOSerialKeys.h>
#import <IOKit/serial/ioss.h>
#import <sys/param.h>
#import <sys/filio.h>
#import <sys/ioctl.h>
#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
#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 int fileDescriptor;
@property (copy, readwrite) NSString *name;
@property (strong) ORSSerialBuffer *requestResponseReceiveBuffer;
// Packet descriptors
@property (nonatomic, strong) NSMapTable *packetDescriptorsAndBuffers;
// Request handling
@property (nonatomic, strong) NSMutableArray *requestsQueue;
@property (nonatomic, strong, readwrite) ORSSerialRequest *pendingRequest;
@property (nonatomic, readwrite) BOOL CTS;
@property (nonatomic, readwrite) BOOL DSR;
@property (nonatomic, readwrite) BOOL DCD;
@property (nonatomic, strong) dispatch_source_t readPollSource;
@property (nonatomic, strong) dispatch_source_t pinPollTimer;
@property (nonatomic, strong) dispatch_source_t pendingRequestTimeoutTimer;
@property (nonatomic, strong) dispatch_queue_t requestHandlingQueue;
@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];
}
- (instancetype)initWithPath:(NSString *)devicePath
{
io_object_t device = [[self class] deviceFromBSDPath:devicePath];
if (device == 0) return nil;
return [self initWithDevice:device];
}
- (instancetype)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.requestHandlingQueue = dispatch_queue_create("com.openreelsoftware.ORSSerialPort.requestHandlingQueue", 0);
self.packetDescriptorsAndBuffers = [NSMapTable strongToStrongObjectsMapTable];
self.requestsQueue = [NSMutableArray array];
self.baudRate = @B19200;
self.allowsNonStandardBaudRates = NO;
self.numberOfStopBits = 1;
self.numberOfDataBits = 8;
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;
}
- (instancetype)init
{
NSAssert(0, @"ORSSerialPort must be init'd using -initWithPath:");
self = [self initWithPath:@""]; // To keep compiler happy.
return self;
}
- (void)dealloc
{
[[self class] removeSerialPort:self];
self.IOKitDevice = 0;
if (_readPollSource) {
dispatch_source_cancel(_readPollSource);
}
if (_pinPollTimer) {
dispatch_source_cancel(_pinPollTimer);
}
if (_pendingRequestTimeoutTimer) {
dispatch_source_cancel(_pendingRequestTimeoutTimer);
}
self.requestHandlingQueue = nil;
}
- (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
- (void)open;
{
if (self.isOpen) return;
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;
}
// 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);
}
self.fileDescriptor = descriptor;
// Port opened successfully, set options
tcgetattr(descriptor, &originalPortAttributes); // Get original options so they can be reset later
[self setPortOptions];
[self updateModemLines];
dispatch_async(mainQueue, ^{
if ([self.delegate respondsToSelector:@selector(serialPortWasOpened:)])
{
[self.delegate serialPortWasOpened:self];
}
});
// Start a read dispatch source in the background
dispatch_source_t readPollSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self.fileDescriptor, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(readPollSource, ^{
__strong typeof(self) strongSelf = weakSelf;
int localPortFD = strongSelf.fileDescriptor;
if (!strongSelf.isOpen) return;
// 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) [strongSelf receiveData:readData];
}
});
dispatch_source_set_cancel_handler(readPollSource, ^{ [weakSelf reallyClosePort]; });
dispatch_resume(readPollSource);
self.readPollSource = readPollSource;
// 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, ^{
__strong typeof(self) strongSelf = weakSelf;
if (!strongSelf.isOpen) {
dispatch_async(pollQueue, ^{ dispatch_source_cancel(timer); });
return;
}
int32_t modemLines=0;
int result = ioctl(strongSelf.fileDescriptor, TIOCMGET, &modemLines);
if (result < 0) {
[strongSelf notifyDelegateOfPosixErrorWaitingUntilDone:(errno == ENXIO)];
if (errno == ENXIO) {
[strongSelf cleanupAfterSystemRemoval];
}
return;
}
BOOL CTSPin = (modemLines & TIOCM_CTS) != 0;
BOOL DSRPin = (modemLines & TIOCM_DSR) != 0;
BOOL DCDPin = (modemLines & TIOCM_CAR) != 0;
if (CTSPin != strongSelf.CTS) { dispatch_sync(mainQueue, ^{ strongSelf.CTS = CTSPin; }); }
dispatch_sync(mainQueue, ^{strongSelf.CTS = CTSPin;});
if (DSRPin != strongSelf.DSR) { dispatch_sync(mainQueue, ^{ strongSelf.DSR = DSRPin; }); }
dispatch_sync(mainQueue, ^{strongSelf.DSR = DSRPin;});
if (DCDPin != strongSelf.DCD) { dispatch_sync(mainQueue, ^{ strongSelf.DCD = DCDPin; }); }
dispatch_sync(mainQueue, ^{strongSelf.DCD = DCDPin;});
});
self.pinPollTimer = timer;
dispatch_resume(self.pinPollTimer);
}
- (BOOL)close;
{
if (!self.isOpen) return YES;
self.readPollSource = nil; // Cancel read dispatch source. Cancel handler will call -reallyClosePort
return YES;
}
- (void)reallyClosePort
{
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);
if (close(self.fileDescriptor)) {
LOG_SERIAL_PORT_ERROR(@"Error closing serial port with file descriptor %i:%i", self.fileDescriptor, errno);
[self notifyDelegateOfPosixError];
return;
}
self.fileDescriptor = 0;
if ([self.delegate respondsToSelector:@selector(serialPortWasClosed:)]) {
[(id)self.delegate performSelectorOnMainThread:@selector(serialPortWasClosed:) withObject:self waitUntilDone:YES];
dispatch_async(self.requestHandlingQueue, ^{
self.requestsQueue = [NSMutableArray array]; // Cancel all queued requests
self.pendingRequest = nil; // Discard pending request
});
}
}
- (void)cleanup;
{
NSLog(@"WARNING: 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 ([self.delegate respondsToSelector:@selector(serialPortWasRemovedFromSystem:)]) {
[(id)self.delegate performSelectorOnMainThread:@selector(serialPortWasRemovedFromSystem:) withObject:self waitUntilDone:YES];
}
[self close];
}
- (BOOL)sendData:(NSData *)data;
{
if (!self.isOpen) return NO;
if ([data length] == 0) return YES;
NSMutableData *writeBuffer = [data mutableCopy];
while ([writeBuffer length] > 0)
{
long numBytesWritten = write(self.fileDescriptor, [writeBuffer bytes], [writeBuffer length]);
if (numBytesWritten < 0) {
LOG_SERIAL_PORT_ERROR(@"Error writing to serial port:%d", errno);
[self notifyDelegateOfPosixError];
return NO;
} else if (numBytesWritten > 0) {
[writeBuffer replaceBytesInRange:NSMakeRange(0, numBytesWritten) withBytes:NULL length:0];
}
}
return YES;
}
- (BOOL)sendRequest:(ORSSerialRequest *)request
{
__block BOOL success = NO;
dispatch_sync(self.requestHandlingQueue, ^{
success = [self reallySendRequest:request];
});
return success;
}
- (void)cancelQueuedRequest:(ORSSerialRequest *)request
{
if (!request) return;
dispatch_async(self.requestHandlingQueue, ^{
if (request == self.pendingRequest) return;
NSInteger requestIndex = [self.requestsQueue indexOfObject:request];
if (requestIndex == NSNotFound) return;
[self removeObjectFromRequestsQueueAtIndex:requestIndex];
});
}
- (void)cancelAllQueuedRequests
{
dispatch_async(self.requestHandlingQueue, ^{
self.requestsQueue = [NSMutableArray array];
});
}
- (void)startListeningForPacketsMatchingDescriptor:(ORSSerialPacketDescriptor *)descriptor;
{
if ([self.packetDescriptorsAndBuffers objectForKey:descriptor]) return; // Already listening
[self willChangeValueForKey:@"packetDescriptorsAndBuffers"];
dispatch_sync(self.requestHandlingQueue, ^{
ORSSerialBuffer *buffer = [[ORSSerialBuffer alloc] initWithMaximumLength:descriptor.maximumPacketLength];
[self.packetDescriptorsAndBuffers setObject:buffer forKey:descriptor];
});
[self didChangeValueForKey:@"packetDescriptorsAndBuffers"];
}
- (void)stopListeningForPacketsMatchingDescriptor:(ORSSerialPacketDescriptor *)descriptor;
{
[self willChangeValueForKey:@"packetDescriptorsAndBuffers"];
dispatch_sync(self.requestHandlingQueue, ^{ [self.packetDescriptorsAndBuffers removeObjectForKey:descriptor]; });
[self didChangeValueForKey:@"packetDescriptorsAndBuffers"];
}
#pragma mark - Private Methods
// Must only be called on requestHandlingQueue (ie. wrap call to this method in dispatch())
- (BOOL)reallySendRequest:(ORSSerialRequest *)request
{
if (!self.pendingRequest) {
NSUInteger bufferLength = request.responseDescriptor.maximumPacketLength;
self.requestResponseReceiveBuffer = [[ORSSerialBuffer alloc] initWithMaximumLength:bufferLength];
// Send immediately
self.pendingRequest = request;
if (request.timeoutInterval > 0) {
NSTimeInterval timeoutInterval = request.timeoutInterval;
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.requestHandlingQueue);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeoutInterval * NSEC_PER_SEC), timeoutInterval * NSEC_PER_SEC, timeoutInterval/10.0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{ [self pendingRequestDidTimeout]; });
self.pendingRequestTimeoutTimer = timer;
dispatch_resume(self.pendingRequestTimeoutTimer);
}
BOOL success = [self sendData:request.dataToSend];
// Immediately send next request if this one doesn't require a response
if (success) [self checkResponseToPendingRequestAndContinueIfValidWithReceivedByte:nil];
return success;
}
// Queue it up to be sent after the pending request is responded to, or times out.
[self insertObject:request inRequestsQueueAtIndex:[self.requestsQueue count]];
return YES;
}
// Must only be called on requestHandlingQueue
- (void)sendNextRequest
{
self.pendingRequest = nil;
if (![self.requestsQueue count]) return;
ORSSerialRequest *nextRequest = self.requestsQueue[0];
[self removeObjectFromRequestsQueueAtIndex:0];
[self reallySendRequest:nextRequest];
}
// Will only be called on requestHandlingQueue
- (void)pendingRequestDidTimeout
{
self.pendingRequestTimeoutTimer = nil;
ORSSerialRequest *request = self.pendingRequest;
if (![self.delegate respondsToSelector:@selector(serialPort:requestDidTimeout:)]) {
[self sendNextRequest];
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate serialPort:self requestDidTimeout:request];
dispatch_async(self.requestHandlingQueue, ^{
[self sendNextRequest];
});
});
}
// Must only be called on requestHandlingQueue
- (void)checkResponseToPendingRequestAndContinueIfValidWithReceivedByte:(NSData *)byte
{
if (!self.pendingRequest) return; // Nothing to do
ORSSerialPacketDescriptor *packetDescriptor = self.pendingRequest.responseDescriptor;
if (!byte) {
if (!packetDescriptor) [self sendNextRequest];
return;
}
[self.requestResponseReceiveBuffer appendData:byte];
NSData *responseData = [packetDescriptor packetMatchingAtEndOfBuffer:self.requestResponseReceiveBuffer.data];
if (!responseData) return;
self.pendingRequestTimeoutTimer = nil;
ORSSerialRequest *request = self.pendingRequest;
dispatch_async(dispatch_get_main_queue(), ^{
if ([responseData length] &&
[self.delegate respondsToSelector:@selector(serialPort:didReceiveResponse:toRequest:)]) {
[self.delegate serialPort:self didReceiveResponse:responseData toRequest:request];
}
});
[self sendNextRequest];
}
#pragma mark Port Read/Write
- (void)receiveData:(NSData *)data;
{
if ([self.delegate respondsToSelector:@selector(serialPort:didReceiveData:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate serialPort:self didReceiveData:data];
});
}
dispatch_async(self.requestHandlingQueue, ^{
const void *bytes = [data bytes];
for (NSUInteger i=0; i<[data length]; i++) {
NSData *byte = [NSData dataWithBytesNoCopy:(void *)(bytes+i) length:1 freeWhenDone:NO];
// Check for packets we're listening for
for (ORSSerialPacketDescriptor *descriptor in self.packetDescriptorsAndBuffers) {
// Append byte to buffer
ORSSerialBuffer *buffer = [self.packetDescriptorsAndBuffers objectForKey:descriptor];
[buffer appendData:byte];
// Check for complete packet
NSData *completePacket = [descriptor packetMatchingAtEndOfBuffer:buffer.data];
if (![completePacket length]) continue;
// Complete packet received, so notify delegate then clear buffer
if ([self.delegate respondsToSelector:@selector(serialPort:didReceivePacket:matchingDescriptor:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate serialPort:self didReceivePacket:completePacket matchingDescriptor:descriptor];
});
}
[buffer clearBuffer];
}
// Also check for response to pending request
[self checkResponseToPendingRequestAndContinueIfValidWithReceivedByte:byte];
}
});
}
#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;
switch (self.numberOfDataBits) {
case 5:
options.c_cflag |= CS5;
break;
case 6:
options.c_cflag |= CS5;
break;
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
break;
}
// 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]);
int result = tcsetattr(self.fileDescriptor, TCSANOW, &options);
if (result != 0) {
if (self.allowsNonStandardBaudRates) {
// Try to set baud rate via ioctl if normal port settings fail
int new_baud = [[self baudRate] intValue];
result = ioctl(self.fileDescriptor, IOSSIOSPEED, &new_baud, 1);
}
if (result != 0) {
// Notify delegate of port error stored in errno
[self notifyDelegateOfPosixError];
}
}
}
+ (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 (![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"];
}
if ([key isEqualToString:@"queuedRequests"]) {
keyPaths = [keyPaths setByAddingObject:@"requestsQueue"];
}
return keyPaths;
}
#pragma mark Port Properties
- (void)insertObject:(ORSSerialRequest *)request inRequestsQueueAtIndex:(NSUInteger)index
{
[self.requestsQueue insertObject:request atIndex:index];
}
- (void)removeObjectFromRequestsQueueAtIndex:(NSUInteger)index
{
[self.requestsQueue removeObjectAtIndex:index];
}
- (NSArray *)queuedRequests
{
return [self.requestsQueue copy];
}
+ (NSSet *)keyPathsForValuesAffectingPacketDescriptors
{
return [NSSet setWithObject:@"packetDescriptorsAndBuffers"];
}
- (NSArray *)packetDescriptors
{
NSArray *result = NSAllMapTableKeys(self.packetDescriptorsAndBuffers);
return result ?: @[];
}
- (BOOL)isOpen { return self.fileDescriptor != 0; }
- (void)setIoKitDevice:(io_object_t)device
{
if (device != _IOKitDevice) {
if (_IOKitDevice) IOObjectRelease(_IOKitDevice);
_IOKitDevice = device;
if (_IOKitDevice) IOObjectRetain(_IOKitDevice);
}
}
- (void)setBaudRate:(NSNumber *)rate
{
if (rate != _baudRate) {
_baudRate = [rate copy];
[self setPortOptions];
}
}
- (void)setNumberOfStopBits:(NSUInteger)num
{
if (num != _numberOfStopBits) {
_numberOfStopBits = num;
[self setPortOptions];
}
}
- (void)setNumberOfDataBits:(NSUInteger)num
{
if (num != _numberOfDataBits)
{
_numberOfDataBits = num;
[self setPortOptions];
}
}
- (void)setShouldEchoReceivedData:(BOOL)flag
{
if (flag != _shouldEchoReceivedData) {
_shouldEchoReceivedData = flag;
[self setPortOptions];
}
}
- (void)setParity:(ORSSerialPortParity)aParity
{
if (aParity != _parity) {
if (aParity != ORSSerialPortParityNone &&
aParity != ORSSerialPortParityOdd &&
aParity != ORSSerialPortParityEven) {
aParity = ORSSerialPortParityNone;
}
_parity = aParity;
[self setPortOptions];
}
}
- (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];
}
}
- (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];
}
}
- (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];
}
}
- (void)updateModemLines
{
if (![self isOpen]) return;
int bits;
ioctl( self.fileDescriptor, TIOCMGET, &bits ) ;
bits = self.RTS ? bits | TIOCM_RTS : bits & ~TIOCM_RTS;
bits = self.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];
}
}
- (void)setRTS:(BOOL)flag
{
if (flag != _RTS)
{
_RTS = flag;
[self updateModemLines];
}
}
- (void)setDTR:(BOOL)flag
{
if (flag != _DTR) {
_DTR = flag;
[self updateModemLines];
}
}
#pragma mark Private Properties
- (void)setReadPollSource:(dispatch_source_t)readPollSource
{
if (readPollSource != _readPollSource) {
if (_readPollSource) {
dispatch_source_cancel(_readPollSource);
}
_readPollSource = readPollSource;
}
}
- (void)setPinPollTimer:(dispatch_source_t)timer
{
if (timer != _pinPollTimer) {
if (_pinPollTimer) {
dispatch_source_cancel(_pinPollTimer);
}
_pinPollTimer = timer;
}
}
- (void)setPendingRequestTimeoutTimer:(dispatch_source_t)pendingRequestTimeoutTimer
{
if (pendingRequestTimeoutTimer != _pendingRequestTimeoutTimer) {
if (_pendingRequestTimeoutTimer) {
dispatch_source_cancel(_pendingRequestTimeoutTimer);
}
_pendingRequestTimeoutTimer = pendingRequestTimeoutTimer;
}
}
@end