// // MGMSerial.m // Rotary Player // // Created by Mr. Gecko's Media (James Coleman) on 1/28/14. // No Copyright Claimed. Public Domain. // #import "MGMSerial.h" #import #import #include NSString * const MGMSerialPortsFoundNotification = @"MGMSerialPortsFoundNotification"; NSString * const MGMSerialPortsRemovedNotification = @"MGMSerialPortsRemovedNotification"; int const MGMSerialPortMaxLine = 4096; @interface MGMSerialPorts (MGMPrivate) - (MGMSerialPort *)nextPort:(io_iterator_t)iterator; @end @interface MGMSerialPort (MGMPrivate) @end void MGMSerialPortFound(void *refcon, io_iterator_t iterator) { } void MGMSerialPortDidRemove(void *refcon, io_iterator_t iterator) { } static MGMSerialPorts *MGMSharedSerialPorts = nil; @implementation MGMSerialPorts + (id)sharedSerialPorts { @synchronized(self) { if (MGMSharedSerialPorts==nil) { MGMSharedSerialPorts = [self new]; } } return MGMSharedSerialPorts; } - (id)init { if ((self = [super init])) { serialPorts = [NSMutableArray new]; CFMutableDictionaryRef servicesMatch = IOServiceMatching(kIOSerialBSDServiceValue); if (servicesMatch!=NULL) { CFDictionarySetValue(servicesMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); CFMutableDictionaryRef servicesMatchRemoved = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, servicesMatch); notificationPort = IONotificationPortCreate(kIOMasterPortDefault); runLoop = IONotificationPortGetRunLoopSource(notificationPort); CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoop, kCFRunLoopDefaultMode); io_iterator_t found; kern_return_t result = IOServiceAddMatchingNotification(notificationPort, kIOPublishNotification, servicesMatch, MGMSerialPortFound, self, &found); if (result!=kIOReturnSuccess) NSLog(@"Unable to register for serial add %d", result); MGMSerialPort *port; while ((port = [self nextPort:found])) { [serialPorts addObject:port]; } IOObjectRelease(found); result = IOServiceAddMatchingNotification(notificationPort, kIOTerminatedNotification, servicesMatchRemoved, MGMSerialPortDidRemove, self, &found); if (result!=kIOReturnSuccess) { NSLog(@"Unable to register for serial remove %d", result); } else { MGMSerialPort *port; while ((port = [self nextPort:found])) { [serialPorts removeObject:port]; } IOObjectRelease(found); } } } return self; } - (MGMSerialPort *)nextPort:(io_iterator_t)iterator { io_object_t serialPort = IOIteratorNext(iterator); if (serialPort != 0) { NSString *portName = [(NSString *)IORegistryEntryCreateCFProperty(serialPort, CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0) autorelease]; NSString *portPath = [(NSString *)IORegistryEntryCreateCFProperty(serialPort, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0) autorelease]; NSString *portType = [(NSString *)IORegistryEntryCreateCFProperty(serialPort, CFSTR(kIOSerialBSDTypeKey), kCFAllocatorDefault, 0) autorelease]; IOObjectRelease(serialPort); if (portName!=nil && portPath!=nil) { MGMSerialPort *port = [self portForPath:portPath]; if (port!=nil) { return port; } return [MGMSerialPort portWithPath:portPath name:portName type:portType speed:0 delegate:nil]; } } return nil; } - (void)portsFound:(io_iterator_t)found { NSMutableArray *addedPorts = [NSMutableArray array]; MGMSerialPort *port; while ((port = [self nextPort:found])) { [serialPorts addObject:port]; [addedPorts addObject:port]; } [[NSNotificationCenter defaultCenter] postNotificationName:MGMSerialPortsFoundNotification object:self userInfo:[NSDictionary dictionaryWithObject:addedPorts forKey:@"ports"]]; } - (void)postsRemoved:(io_iterator_t)removed { NSMutableArray *removedPorts = [NSMutableArray array]; MGMSerialPort *port; while ((port = [self nextPort:removed])) { [port close]; [removedPorts addObject:port]; [serialPorts removeObject:port]; } [[NSNotificationCenter defaultCenter] postNotificationName:MGMSerialPortsRemovedNotification object:self userInfo:[NSDictionary dictionaryWithObject:removedPorts forKey:@"ports"]]; } - (NSArray *)serialPorts { return serialPorts; } - (MGMSerialPort *)portForName:(NSString *)portName { for (int i=0; i<[serialPorts count]; i++) { if ([[(MGMSerialPort *)[serialPorts objectAtIndex:i] name] isEqualToString:portName]) return [serialPorts objectAtIndex:i]; } return nil; } - (MGMSerialPort *)portForPath:(NSString *)portPath { for (int i=0; i<[serialPorts count]; i++) { if ([[(MGMSerialPort *)[serialPorts objectAtIndex:i] path] isEqualToString:portPath]) return [serialPorts objectAtIndex:i]; } return nil; } - (NSArray *)portsOfType:(NSString *)portType { NSMutableArray *ports = [NSMutableArray array]; for (int i=0; i<[serialPorts count]; i++) { if ([[(MGMSerialPort *)[serialPorts objectAtIndex:i] type] isEqualToString:portType]) [ports addObject:[serialPorts objectAtIndex:i]]; } return ports; } @end @implementation MGMSerialPort + (id)portWithPath:(NSString *)thePath name:(NSString *)theName type:(NSString *)theType speed:(int)theSpeed delegate:(id)theDelegate { return [[[self alloc] initWithPath:thePath name:theName type:theType speed:theSpeed delegate:theDelegate] autorelease]; } - (id)initWithPath:(NSString *)thePath name:(NSString *)theName type:(NSString *)theType speed:(int)theSpeed delegate:(id)theDelegate { if ((self = [super init])) { portPath = [thePath copy]; portName = [theName copy]; portType = [theType copy]; portSpeed = theSpeed; fileDescriptor = -1; readLock = [NSLock new]; closeLock = [NSLock new]; delegate = [theDelegate retain]; stopBackgroundRead = YES; } return self; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p %@ %@>", NSStringFromClass([self class]), self, portPath, portName]; } - (NSString *)path { return portPath; } - (NSString *)name { return portName; } - (NSString *)type { return portType; } - (BOOL)isOpen { return (fileDescriptor>=0); } - (BOOL)open { if (fileDescriptor>=0) return YES; fileDescriptor = open([portPath fileSystemRepresentation], O_RDWR | O_NOCTTY | O_NONBLOCK); if (fileDescriptor<0) { fileDescriptor = -1; NSLog(@"Unable to open."); return NO; } else if (portSpeed!=0) { speed_t newSpeed = portSpeed; int errorCode = ioctl(fileDescriptor, IOSSIOSPEED, &newSpeed, 1); if (errorCode==-1) { if (fileDescriptor>=0) close(fileDescriptor); fileDescriptor = -1; } } return YES; } - (void)close { if (fileDescriptor>=0) { stopBackgroundRead = YES; [closeLock lock]; close(fileDescriptor); fileDescriptor = -1; [closeLock unlock]; } } - (long)speed { return portSpeed; } - (BOOL)setSpeed:(long)theSpeed { if (fileDescriptor >= 0) { speed_t newSpeed = theSpeed; int errorCode = ioctl(fileDescriptor, IOSSIOSPEED, &newSpeed, 1); if (errorCode == -1) { return NO; } else { portSpeed = theSpeed; } } else { portSpeed = theSpeed; } return YES; } - (id)delegate { return delegate; } - (void)setDelegate:(id)theDelegate { [delegate autorelease]; delegate = [theDelegate retain]; } - (BOOL)writeData:(NSData *)data { const char *dataBytes = (const char *)[data bytes]; NSUInteger dataLenth = [data length]; ssize_t bytesWritten = 0; if (dataBytes!=NULL && dataLenth>0) { bytesWritten = write(fileDescriptor, dataBytes, dataLenth); if ((NSUInteger)bytesWritten==dataLenth) { return YES; } } return NO; } - (BOOL)writeString:(NSString *)string usingEncoding:(NSStringEncoding)encoding { return [self writeData:[string dataUsingEncoding:encoding]]; } - (NSData *)readData:(int)theByteCount { [readLock lock]; [closeLock lock]; NSData *data = nil; void *buffer = malloc(theByteCount); ssize_t bytesRead = 0; fd_set *readFDs = NULL; if (fileDescriptor>=0) { readFDs = (fd_set *)malloc(sizeof(fd_set)); FD_ZERO(readFDs); FD_SET(fileDescriptor, readFDs); int result = select(fileDescriptor+1, readFDs, nil, nil, nil); if (result>=1 && fileDescriptor>=0) bytesRead = read(fileDescriptor, buffer, theByteCount); free(readFDs); readFDs = NULL; if (bytesRead==0) { [closeLock unlock]; [readLock unlock]; free(buffer); return nil; } data = [NSData dataWithBytes:buffer length:bytesRead]; } free(buffer); [closeLock unlock]; [readLock unlock]; return data; } - (BOOL)readDataInBackgroundNewLine { if ([delegate respondsToSelector:@selector(serial:read:)] && stopBackgroundRead && fileDescriptor>=0) { stopBackgroundRead = NO; [NSThread detachNewThreadSelector:@selector(readDataBackgroundThread) toTarget:self withObject:nil]; return YES; } return NO; } - (void)stopBackgroundRead { stopBackgroundRead = YES; } - (void)readDataBackgroundThread { NSAutoreleasePool *pool = [NSAutoreleasePool new]; while (!stopBackgroundRead) { NSMutableData *data = [NSMutableData new]; while (!stopBackgroundRead) { NSData *readData = [self readData:1]; if (readData!=nil) { if (strcmp([readData bytes], "\n")==0 || strcmp([readData bytes], "\r")==0) { if ([data length]<=0) continue; break; } else { if ([data length]>=MGMSerialPortMaxLine) break; [data appendData:readData]; } } [pool drain]; pool = [NSAutoreleasePool new]; } if ([data length]>0 && !stopBackgroundRead) { SEL readSelector = @selector(serial:read:); NSMethodSignature *signature = [(NSObject *)delegate methodSignatureForSelector:readSelector]; if (signature!=nil) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocation setSelector:readSelector]; [invocation setArgument:&self atIndex:2]; [invocation setArgument:&data atIndex:3]; [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:delegate waitUntilDone:YES]; } } [data release]; } [pool drain]; } @end