1270 lines
42 KiB
Objective-C
Executable File
1270 lines
42 KiB
Objective-C
Executable File
@import Cocoa;
|
|
@import LuaSkin;
|
|
|
|
#import "ORSSerialPort/ORSSerialPort.h"
|
|
#import "ORSSerialPort/ORSSerialPortManager.h"
|
|
|
|
#import <IOKit/usb/USBSpec.h>
|
|
|
|
#define USERDATA_TAG "hs.serial"
|
|
static LSRefTable refTable = LUA_NOREF;
|
|
|
|
#define get_objectFromUserdata(objType, L, idx, tag) (objType*)*((void**)luaL_checkudata(L, idx, tag))
|
|
|
|
#pragma mark - ORSSerial Additions
|
|
|
|
@interface ORSSerialPort (Attributes)
|
|
@property (nonatomic, readonly) NSDictionary *ioDeviceAttributes;
|
|
@end
|
|
|
|
@implementation ORSSerialPort (Attributes)
|
|
|
|
- (NSDictionary *)ioDeviceAttributes
|
|
{
|
|
NSDictionary *result = nil;
|
|
|
|
io_iterator_t iterator = 0;
|
|
if (IORegistryEntryCreateIterator(self.IOKitDevice,
|
|
kIOServicePlane,
|
|
kIORegistryIterateRecursively + kIORegistryIterateParents,
|
|
&iterator) != KERN_SUCCESS) return nil;
|
|
|
|
io_object_t device = 0;
|
|
while ((device = IOIteratorNext(iterator)) && result == nil)
|
|
{
|
|
CFMutableDictionaryRef usbProperties = 0;
|
|
if (IORegistryEntryCreateCFProperties(device, &usbProperties, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS)
|
|
{
|
|
IOObjectRelease(device);
|
|
continue;
|
|
}
|
|
NSDictionary *properties = CFBridgingRelease(usbProperties);
|
|
|
|
NSNumber *vendorID = properties[(__bridge NSString *)CFSTR(kUSBVendorID)];
|
|
NSNumber *productID = properties[(__bridge NSString *)CFSTR(kUSBProductID)];
|
|
if (!vendorID || !productID) { IOObjectRelease(device); continue; } // not a USB device
|
|
|
|
result = properties;
|
|
|
|
IOObjectRelease(device);
|
|
}
|
|
|
|
IOObjectRelease(iterator);
|
|
return result;
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - String Conversion
|
|
|
|
@implementation NSData (NSData_Conversion)
|
|
// Returns hexadecimal string of NSData. Empty string if data is empty.
|
|
- (NSString *)hexadecimalString
|
|
{
|
|
const unsigned char *dataBuffer = (const unsigned char *)[self bytes];
|
|
|
|
if (!dataBuffer)
|
|
{
|
|
return [NSString string];
|
|
}
|
|
|
|
NSUInteger dataLength = [self length];
|
|
NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
|
|
|
|
for (int i = 0; i < dataLength; ++i)
|
|
{
|
|
[hexString appendFormat:@"%02x", (unsigned int)dataBuffer[i]];
|
|
}
|
|
|
|
return [NSString stringWithString:hexString];
|
|
}
|
|
@end
|
|
|
|
#pragma mark - Support Functions and Classes
|
|
|
|
@interface HSSerialPort : NSObject <ORSSerialPortDelegate>
|
|
|
|
@property (nonatomic, strong) ORSSerialPortManager *serialPortManager;
|
|
@property (nonatomic, strong) ORSSerialPort *serialPort;
|
|
|
|
@property int selfRefCount;
|
|
@property int callbackRef;
|
|
@property id callbackToken;
|
|
@property int deviceCallbackRef;
|
|
|
|
@property NSString* portName;
|
|
@property NSString* portPath;
|
|
|
|
@property LSGCCanary lsCanary;
|
|
|
|
@property ORSSerialPortParity parity;
|
|
@property NSNumber* baudRate;
|
|
@property NSUInteger numberOfStopBits;
|
|
@property NSUInteger numberOfDataBits;
|
|
@property BOOL shouldEchoReceivedData;
|
|
@property BOOL usesRTSCTSFlowControl;
|
|
@property BOOL usesDTRDSRFlowControl;
|
|
@property BOOL usesDCDOutputFlowControl;
|
|
@property BOOL allowsNonStandardBaudRates;
|
|
|
|
@end
|
|
|
|
@implementation HSSerialPort
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
self.serialPortManager = [ORSSerialPortManager sharedSerialPortManager];
|
|
|
|
_callbackRef = LUA_NOREF;
|
|
_deviceCallbackRef = LUA_NOREF;
|
|
_callbackToken = nil;
|
|
_selfRefCount = 0;
|
|
|
|
_portName = nil;
|
|
_portPath = nil;
|
|
_parity = ORSSerialPortParityNone;
|
|
_baudRate = [NSNumber numberWithInt:115200];
|
|
_numberOfStopBits = 1;
|
|
_numberOfDataBits = 8;
|
|
_shouldEchoReceivedData = NO;
|
|
_usesRTSCTSFlowControl = NO;
|
|
_usesDTRDSRFlowControl = NO;
|
|
_usesDCDOutputFlowControl = NO;
|
|
_allowsNonStandardBaudRates = NO;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
#pragma mark - hs.serial.deviceCallback Functions
|
|
|
|
- (void)watchDevices
|
|
{
|
|
@try {
|
|
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
[nc addObserver:self selector:@selector(serialPortsWereConnected:) name:ORSSerialPortsWereConnectedNotification object:nil];
|
|
[nc addObserver:self selector:@selector(serialPortsWereDisconnected:) name:ORSSerialPortsWereDisconnectedNotification object:nil];
|
|
}
|
|
@catch (NSException *exception) {
|
|
[LuaSkin logError:[NSString stringWithFormat:@"%s:deviceCallback - %@", USERDATA_TAG, exception.reason]];
|
|
}
|
|
}
|
|
|
|
- (void)unwatchDevices
|
|
{
|
|
@try {
|
|
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
[nc removeObserver:self name:ORSSerialPortsWereConnectedNotification object:nil];
|
|
[nc removeObserver:self name:ORSSerialPortsWereDisconnectedNotification object:nil];
|
|
}
|
|
@catch (NSException *exception) {
|
|
[LuaSkin logError:[NSString stringWithFormat:@"%s:deviceCallback - %@", USERDATA_TAG, exception.reason]];
|
|
}
|
|
}
|
|
|
|
#pragma mark - ORSSerialPortDelegate Methods
|
|
|
|
- (void)serialPortWasOpened:(ORSSerialPort *)serialPort
|
|
{
|
|
if (_callbackRef != LUA_NOREF) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:refTable ref:_callbackRef];
|
|
[skin pushNSObject:self];
|
|
[skin pushNSObject:@"opened"];
|
|
[skin protectedCallAndError:@"hs.serial:callback" nargs:2 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
}
|
|
|
|
- (void)serialPortWasClosed:(ORSSerialPort *)serialPort
|
|
{
|
|
if (_callbackRef != LUA_NOREF) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:refTable ref:_callbackRef];
|
|
[skin pushNSObject:self];
|
|
[skin pushNSObject:@"closed"];
|
|
[skin protectedCallAndError:@"hs.serial:callback" nargs:2 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
}
|
|
|
|
- (void)serialPort:(ORSSerialPort *)serialPort didReceiveData:(NSData *)data
|
|
{
|
|
if (_callbackRef != LUA_NOREF) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:refTable ref:_callbackRef];
|
|
[skin pushNSObject:self];
|
|
[skin pushNSObject:@"received"];
|
|
[skin pushNSObject:data];
|
|
NSString *hex = [data hexadecimalString];
|
|
[skin pushNSObject:hex];
|
|
[skin protectedCallAndError:@"hs.serial:callback" nargs:4 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
}
|
|
|
|
- (void)serialPort:(ORSSerialPort *)serialPort didReceivePacket:(NSData *)packetData matchingDescriptor:(ORSSerialPacketDescriptor *)descriptor
|
|
{
|
|
// TODO: Impliment `ORSSerialPacketDescriptor` functionality
|
|
}
|
|
|
|
- (void)serialPortWasRemovedFromSystem:(ORSSerialPort *)serialPort;
|
|
{
|
|
if (_callbackRef != LUA_NOREF) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:refTable ref:_callbackRef];
|
|
[skin pushNSObject:self];
|
|
[skin pushNSObject:@"removed"];
|
|
[skin protectedCallAndError:@"hs.serial:callback" nargs:2 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
|
|
// After a serial port is removed from the system, it is invalid and we must discard any references to it:
|
|
self.serialPort = nil;
|
|
}
|
|
|
|
- (void)serialPort:(ORSSerialPort *)serialPort didEncounterError:(NSError *)error
|
|
{
|
|
if (_callbackRef != LUA_NOREF) {
|
|
NSString *errorString = [NSString stringWithFormat:@"%@", error];
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:refTable ref:_callbackRef];
|
|
[skin pushNSObject:self];
|
|
[skin pushNSObject:@"error"];
|
|
[skin pushNSObject:errorString];
|
|
[skin protectedCallAndError:@"hs.serial:callback" nargs:3 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
}
|
|
|
|
#pragma mark - ORSSerialPortManager Notifications
|
|
|
|
- (void)serialPortsWereConnected:(NSNotification *)notification
|
|
{
|
|
if (_deviceCallbackRef != LUA_NOREF) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:refTable ref:_deviceCallbackRef];
|
|
[skin pushNSObject:@"connected"];
|
|
|
|
// Get a table of connected port names:
|
|
NSArray *connectedPorts = [notification userInfo][ORSConnectedSerialPortsKey];
|
|
NSMutableArray *result = [[NSMutableArray alloc] init];
|
|
for (ORSSerialPort *port in connectedPorts)
|
|
{
|
|
NSString *portName = [port name];
|
|
[result addObject:portName];
|
|
}
|
|
[skin pushNSObject:result];
|
|
[skin protectedCallAndError:@"hs.serial:deviceCallback" nargs:2 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
}
|
|
|
|
- (void)serialPortsWereDisconnected:(NSNotification *)notification
|
|
{
|
|
if (_deviceCallbackRef != LUA_NOREF) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
|
|
if (![skin checkGCCanary:self.lsCanary]) {
|
|
return;
|
|
}
|
|
|
|
_lua_stackguard_entry(skin.L);
|
|
[skin pushLuaRef:refTable ref:_deviceCallbackRef];
|
|
[skin pushNSObject:@"disconnected"];
|
|
|
|
// Get a table of disconnected port names:
|
|
NSArray *disconnectedPorts = [notification userInfo][ORSDisconnectedSerialPortsKey];
|
|
NSMutableArray *result = [[NSMutableArray alloc] init];
|
|
for (ORSSerialPort *port in disconnectedPorts)
|
|
{
|
|
NSString *portName = [port name];
|
|
[result addObject:portName];
|
|
}
|
|
[skin pushNSObject:result];
|
|
[skin protectedCallAndError:@"hs.serial:deviceCallback" nargs:2 nresults:0];
|
|
_lua_stackguard_exit(skin.L);
|
|
}
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
- (bool)isPortNameValid:(NSString *)portName
|
|
{
|
|
NSArray *availablePorts = _serialPortManager.availablePorts;
|
|
for (ORSSerialPort *port in availablePorts)
|
|
{
|
|
NSString *currentName = [port name];
|
|
if ([portName isEqualToString:currentName]) {
|
|
self.portName = portName;
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (bool)isPathValid:(NSString *)path
|
|
{
|
|
NSArray *availablePorts = _serialPortManager.availablePorts;
|
|
for (ORSSerialPort *port in availablePorts)
|
|
{
|
|
NSString *currentPath = [port path];
|
|
if ([path isEqualToString:currentPath]) {
|
|
self.portPath = [port path];
|
|
self.portName = [port name];
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (bool)createPortFromPortName:(NSString *)portName
|
|
{
|
|
NSArray *availablePorts = _serialPortManager.availablePorts;
|
|
for (ORSSerialPort *port in availablePorts)
|
|
{
|
|
NSString *currentName = [port name];
|
|
if ([portName isEqualToString:currentName]) {
|
|
NSString *currentPath = [port path];
|
|
|
|
[_serialPort close];
|
|
_serialPort.delegate = nil;
|
|
|
|
_serialPort = [ORSSerialPort serialPortWithPath:currentPath];
|
|
_serialPort.delegate = self;
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (bool)createPortFromPath:(NSString *)portPath
|
|
{
|
|
[_serialPort close];
|
|
_serialPort.delegate = nil;
|
|
|
|
_serialPort = [ORSSerialPort serialPortWithPath:portPath];
|
|
_serialPort.delegate = self;
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)open
|
|
{
|
|
if (!self.serialPort && self.portPath) {
|
|
[self createPortFromPath:self.portPath];
|
|
}
|
|
if (!self.serialPort && self.portName) {
|
|
[self createPortFromPortName:self.portName];
|
|
}
|
|
if (self.serialPort) {
|
|
self.serialPort.allowsNonStandardBaudRates = self.allowsNonStandardBaudRates;
|
|
self.serialPort.parity = self.parity;
|
|
self.serialPort.baudRate = self.baudRate;
|
|
self.serialPort.numberOfStopBits = self.numberOfStopBits;
|
|
self.serialPort.numberOfDataBits = self.numberOfDataBits;
|
|
self.serialPort.shouldEchoReceivedData = self.shouldEchoReceivedData;
|
|
self.serialPort.usesRTSCTSFlowControl = self.usesRTSCTSFlowControl;
|
|
self.serialPort.usesDTRDSRFlowControl = self.usesDTRDSRFlowControl;
|
|
self.serialPort.usesDCDOutputFlowControl = self.usesDCDOutputFlowControl;
|
|
[self.serialPort open];
|
|
}
|
|
return self.serialPort && self.serialPort.isOpen;
|
|
}
|
|
|
|
- (void)changeParity:(ORSSerialPortParity)parity
|
|
{
|
|
self.parity = parity;
|
|
if ([self isOpen]) {
|
|
self.serialPort.parity = parity;
|
|
}
|
|
}
|
|
|
|
- (void)changeBaudRate:(NSNumber*)baudRate
|
|
{
|
|
self.baudRate = baudRate;
|
|
if ([self isOpen]) {
|
|
self.serialPort.allowsNonStandardBaudRates = self.allowsNonStandardBaudRates;
|
|
self.serialPort.baudRate = baudRate;
|
|
}
|
|
}
|
|
|
|
- (void)changeNumberOfStopBits:(NSUInteger)numberOfStopBits
|
|
{
|
|
self.numberOfStopBits = numberOfStopBits;
|
|
if ([self isOpen]) {
|
|
self.serialPort.numberOfStopBits = numberOfStopBits;
|
|
}
|
|
}
|
|
|
|
- (void)changeNumberOfDataBits:(NSUInteger)numberOfDataBits
|
|
{
|
|
self.numberOfDataBits = numberOfDataBits;
|
|
if ([self isOpen]) {
|
|
self.serialPort.numberOfDataBits = numberOfDataBits;
|
|
}
|
|
}
|
|
|
|
- (void)changeUsesRTSCTSFlowControl:(BOOL)usesRTSCTSFlowControl
|
|
{
|
|
self.usesRTSCTSFlowControl = usesRTSCTSFlowControl;
|
|
if ([self isOpen]) {
|
|
self.serialPort.usesRTSCTSFlowControl = usesRTSCTSFlowControl;
|
|
}
|
|
}
|
|
|
|
- (void)changeUsesDTRDSRFlowControl:(BOOL)usesDTRDSRFlowControl
|
|
{
|
|
self.usesDTRDSRFlowControl = usesDTRDSRFlowControl;
|
|
if ([self isOpen]) {
|
|
self.serialPort.usesDTRDSRFlowControl = usesDTRDSRFlowControl;
|
|
}
|
|
}
|
|
|
|
- (void)changeUsesDCDOutputFlowControl:(BOOL)usesDCDOutputFlowControl
|
|
{
|
|
self.usesDCDOutputFlowControl = usesDCDOutputFlowControl;
|
|
if ([self isOpen]) {
|
|
self.serialPort.usesDCDOutputFlowControl = usesDCDOutputFlowControl;
|
|
}
|
|
}
|
|
|
|
- (void)changeShouldEchoReceivedData:(BOOL)shouldEchoReceivedData
|
|
{
|
|
self.shouldEchoReceivedData = shouldEchoReceivedData;
|
|
if ([self isOpen]) {
|
|
self.serialPort.shouldEchoReceivedData = shouldEchoReceivedData;
|
|
}
|
|
}
|
|
|
|
- (void)close
|
|
{
|
|
if (self.serialPort) {
|
|
[self.serialPort close];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isOpen
|
|
{
|
|
return self.serialPort && self.serialPort.isOpen;
|
|
}
|
|
|
|
- (void)sendData:(NSData*)dataToSend
|
|
{
|
|
if (self.serialPort) {
|
|
[self.serialPort sendData:dataToSend];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
/// hs.serial.newFromName(portName) -> serialPortObject
|
|
/// Constructor
|
|
/// Creates a new `hs.serial` object using the port name.
|
|
///
|
|
/// Parameters:
|
|
/// * portName - A string containing the port name.
|
|
///
|
|
/// Returns:
|
|
/// * An `hs.serial` object or `nil` if an error occured.
|
|
///
|
|
/// Notes:
|
|
/// * A valid port name can be found by checking `hs.serial.availablePortNames()`.
|
|
static int serial_newFromName(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TSTRING, LS_TBREAK];
|
|
|
|
NSString *portName = [skin toNSObjectAtIndex:1];
|
|
|
|
HSSerialPort *serialPort = [[HSSerialPort alloc] init];
|
|
|
|
serialPort.lsCanary = [skin createGCCanary];
|
|
|
|
bool result = [serialPort isPortNameValid:portName];
|
|
|
|
if (serialPort && result) {
|
|
[skin pushNSObject:serialPort];
|
|
} else {
|
|
serialPort = nil;
|
|
lua_pushnil(L);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial.newFromPath(path) -> serialPortObject
|
|
/// Constructor
|
|
/// Creates a new `hs.serial` object using a path.
|
|
///
|
|
/// Parameters:
|
|
/// * path - A string containing the path (i.e. "/dev/cu.usbserial").
|
|
///
|
|
/// Returns:
|
|
/// * An `hs.serial` object or `nil` if an error occured.
|
|
///
|
|
/// Notes:
|
|
/// * A valid port name can be found by checking `hs.serial.availablePortPaths()`.
|
|
static int serial_newFromPath(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TSTRING, LS_TBREAK];
|
|
|
|
NSString *path = [skin toNSObjectAtIndex:1];
|
|
|
|
HSSerialPort *serialPort = [[HSSerialPort alloc] init];
|
|
|
|
serialPort.lsCanary = [skin createGCCanary];
|
|
|
|
bool result = [serialPort isPathValid:path];
|
|
|
|
if (serialPort && result) {
|
|
[skin pushNSObject:serialPort];
|
|
} else {
|
|
serialPort = nil;
|
|
lua_pushnil(L);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:callback(callbackFn) -> serialPortObject
|
|
/// Method
|
|
/// Sets or removes a callback function for the `hs.serial` object.
|
|
///
|
|
/// Parameters:
|
|
/// * `callbackFn` - a function to set as the callback for this `hs.serial` object. If the value provided is `nil`, any currently existing callback function is removed.
|
|
///
|
|
/// Returns:
|
|
/// * The `hs.serial` object
|
|
///
|
|
/// Notes:
|
|
/// * The callback function should expect 4 arguments and should not return anything:
|
|
/// * `serialPortObject` - The serial port object that triggered the callback.
|
|
/// * `callbackType` - A string containing "opened", "closed", "received", "removed" or "error".
|
|
/// * `message` - If the `callbackType` is "received", then this will be the data received as a string. If the `callbackType` is "error", this will be the error message as a string.
|
|
/// * `hexadecimalString` - If the `callbackType` is "received", then this will be the data received as a hexadecimal string.
|
|
static int serial_callback(lua_State *L) {
|
|
// Check Arguments:
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TFUNCTION | LS_TNIL, LS_TBREAK];
|
|
|
|
// Get Serial Port:
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
|
|
// Remove the existing callback:
|
|
serialPort.callbackRef = [skin luaUnref:refTable ref:serialPort.callbackRef];
|
|
if (serialPort.callbackToken != nil) {
|
|
serialPort.callbackToken = nil;
|
|
}
|
|
|
|
// Setup the new callback:
|
|
if (lua_type(L, 2) != LUA_TNIL) { // may be table with __call metamethod
|
|
lua_pushvalue(L, 2);
|
|
serialPort.callbackRef = [skin luaRef:refTable];
|
|
}
|
|
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial.availablePortNames() -> table
|
|
/// Function
|
|
/// Returns a table of currently connected serial ports names.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * A table containing the names of any connected serial port names as strings.
|
|
static int serial_availablePortNames(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs: LS_TBREAK];
|
|
ORSSerialPortManager *portManager = [ORSSerialPortManager sharedSerialPortManager];
|
|
NSArray *availablePorts = portManager.availablePorts;
|
|
NSMutableArray *result = [[NSMutableArray alloc] init];
|
|
for (ORSSerialPort *port in availablePorts)
|
|
{
|
|
NSString *portName = [port name];
|
|
[result addObject:portName];
|
|
}
|
|
[skin pushNSObject:result];
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial.availablePortDetails() -> table
|
|
/// Function
|
|
/// Returns a table of currently connected serial ports details, organised by port name.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * A table containing the IOKit details of any connected serial ports, organised by port name.
|
|
static int serial_availablePortDetails(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs: LS_TBREAK];
|
|
ORSSerialPortManager *portManager = [ORSSerialPortManager sharedSerialPortManager];
|
|
NSArray *availablePorts = portManager.availablePorts;
|
|
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
|
for (ORSSerialPort *port in availablePorts)
|
|
{
|
|
NSString *portName = [port name];
|
|
NSDictionary *attributes = [port ioDeviceAttributes];
|
|
if (attributes == nil) {
|
|
attributes = @{};
|
|
}
|
|
[result setObject:attributes forKey:portName];
|
|
}
|
|
[skin pushNSObject:result];
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial.availablePortPaths() -> table
|
|
/// Function
|
|
/// Returns a table of currently connected serial ports paths.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * A table containing the names of any connected serial port paths as strings.
|
|
static int serial_availablePortPaths(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs: LS_TBREAK];
|
|
ORSSerialPortManager *portManager = [ORSSerialPortManager sharedSerialPortManager];
|
|
NSArray *availablePorts = portManager.availablePorts;
|
|
NSMutableArray *result = [[NSMutableArray alloc] init];
|
|
for (ORSSerialPort *port in availablePorts)
|
|
{
|
|
NSString *portPath = [port path];
|
|
[result addObject:portPath];
|
|
}
|
|
[skin pushNSObject:result];
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:name() -> string
|
|
/// Method
|
|
/// Returns the name of a `hs.serial` object.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * The name as a string.
|
|
static int serial_name(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
NSString *deviceName = [serialPort.serialPort name];
|
|
[skin pushNSObject:deviceName];
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:path() -> string
|
|
/// Method
|
|
/// Returns the path of a `hs.serial` object.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * The path as a string.
|
|
static int serial_path(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
NSString *portPath = [serialPort.serialPort path];
|
|
[skin pushNSObject:portPath];
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:open() -> serialPortObject | nil
|
|
/// Method
|
|
/// Opens the serial port.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * The `hs.serial` object or `nil` if the port could not be opened.
|
|
static int serial_open(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
BOOL result = [serialPort open];
|
|
if (result) {
|
|
lua_pushvalue(L, 1);
|
|
} else {
|
|
lua_pushnil(L);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:close() -> serialPortObject
|
|
/// Method
|
|
/// Closes the serial port.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * The `hs.serial` object.
|
|
static int serial_close(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
[serialPort close];
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:baudRate([value], [allowNonStandardBaudRates]) -> number | serialPortObject
|
|
/// Method
|
|
/// Gets or sets the baud rate for the serial port.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional number to set the baud rate.
|
|
/// * [allowNonStandardBaudRates] - An optional boolean to enable non-standard baud rates. Defaults to `false`.
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns the baud rate as a number
|
|
///
|
|
/// Notes:
|
|
/// * This function supports the following standard baud rates as numbers: 300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200, 230400.
|
|
/// * If no baud rate is supplied, it defaults to 115200.
|
|
static int serial_baudRate(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK];
|
|
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
NSNumber *baudRate;
|
|
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
baudRate = serialPort.baudRate;
|
|
[skin pushNSObject:baudRate];
|
|
}
|
|
else {
|
|
// Set:
|
|
NSNumber *proposedBaudRate = [skin toNSObjectAtIndex:2];
|
|
|
|
BOOL allowNonStandardBaudRates = (lua_isboolean(L, 3) && lua_toboolean(L, 3));
|
|
if (allowNonStandardBaudRates) {
|
|
serialPort.allowsNonStandardBaudRates = YES;
|
|
[serialPort changeBaudRate:proposedBaudRate];
|
|
lua_pushvalue(L, 1);
|
|
} else {
|
|
NSArray *availableBaudRates = @[@300, @1200, @2400, @4800, @9600, @14400, @19200, @28800, @38400, @57600, @115200, @230400];
|
|
if ([availableBaudRates containsObject:proposedBaudRate]) {
|
|
// Valid Baud Rate:
|
|
[serialPort changeBaudRate:proposedBaudRate];
|
|
}
|
|
else {
|
|
[skin logError:[NSString stringWithFormat:@"%s: Invalid Baud Rate supplied. Possible baud rates are: 300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 and 230400.", USERDATA_TAG]];
|
|
}
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:parity([value]) -> string | serialPortObject
|
|
/// Method
|
|
/// Gets or sets the parity for the serial port.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional string to set the parity. It can be "none", "odd" or "even".
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns a string value of "none", "odd" or "even".
|
|
static int serial_parity(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING | LS_TOPTIONAL, LS_TBREAK];
|
|
|
|
NSString *result;
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
ORSSerialPortParity parity = [serialPort.serialPort parity];
|
|
if (parity == ORSSerialPortParityNone) {
|
|
result = @"none";
|
|
}
|
|
else if (parity == ORSSerialPortParityOdd) {
|
|
result = @"odd";
|
|
}
|
|
else if (parity == ORSSerialPortParityEven) {
|
|
result = @"even";
|
|
}
|
|
[skin pushNSObject:result];
|
|
} else {
|
|
// Set:
|
|
NSArray *availableParity = @[@"none", @"odd", @"even"];
|
|
NSString *proposedParity = [skin toNSObjectAtIndex:2];
|
|
if ([availableParity containsObject:proposedParity]) {
|
|
// Valid Parity Rate:
|
|
ORSSerialPortParity newParity;
|
|
if ([proposedParity isEqualToString:@"odd"]) {
|
|
newParity = ORSSerialPortParityOdd;
|
|
}
|
|
else if ([proposedParity isEqualToString:@"even"]) {
|
|
newParity = ORSSerialPortParityEven;
|
|
} else {
|
|
newParity = ORSSerialPortParityNone;
|
|
}
|
|
[serialPort changeParity:newParity];
|
|
} else {
|
|
[skin logError:[NSString stringWithFormat:@"%s: Invalid Parity string supplied. Should be 'none', 'odd' or 'even'.", USERDATA_TAG]];
|
|
}
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:usesDTRDSRFlowControl([value]) -> boolean | serialPortObject
|
|
/// Method
|
|
/// Gets or sets whether the port should use DCD Flow Control.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional boolean.
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns a boolean.
|
|
/// * The default value is `false`.
|
|
static int serial_usesDCDOutputFlowControl(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
BOOL usesDCDOutputFlowControl = serialPort.usesDCDOutputFlowControl;
|
|
lua_pushboolean(L, usesDCDOutputFlowControl);
|
|
} else {
|
|
bool usesDCDOutputFlowControl = lua_toboolean(L, 2);
|
|
[serialPort changeUsesDCDOutputFlowControl:usesDCDOutputFlowControl];
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:usesDTRDSRFlowControl([value]) -> boolean | serialPortObject
|
|
/// Method
|
|
/// Gets or sets whether the port should use DTR/DSR Flow Control.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional boolean.
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns a boolean.
|
|
/// * The default value is `false`.
|
|
static int serial_usesDTRDSRFlowControl(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
BOOL usesDTRDSRFlowControl = serialPort.usesDTRDSRFlowControl;
|
|
lua_pushboolean(L, usesDTRDSRFlowControl);
|
|
} else {
|
|
BOOL usesDTRDSRFlowControl = lua_toboolean(L, 2);
|
|
[serialPort changeUsesDTRDSRFlowControl:usesDTRDSRFlowControl];
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:usesRTSCTSFlowControl([value]) -> boolean | serialPortObject
|
|
/// Method
|
|
/// Gets or sets whether the port should use RTS/CTS Flow Control.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional boolean.
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns a boolean.
|
|
/// * The default value is `false`.
|
|
static int serial_usesRTSCTSFlowControl(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
BOOL usesRTSCTSFlowControl = serialPort.usesRTSCTSFlowControl;
|
|
lua_pushboolean(L, usesRTSCTSFlowControl);
|
|
} else {
|
|
BOOL usesRTSCTSFlowControl = lua_toboolean(L, 2);
|
|
[serialPort changeUsesRTSCTSFlowControl:usesRTSCTSFlowControl];
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:shouldEchoReceivedData([value]) -> boolean | serialPortObject
|
|
/// Method
|
|
/// Gets or sets whether the port should echo received data.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional boolean.
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns a boolean.
|
|
/// * The default value is `false`.
|
|
static int serial_shouldEchoReceivedData(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
BOOL shouldEchoReceivedData = serialPort.shouldEchoReceivedData;
|
|
lua_pushboolean(L, shouldEchoReceivedData);
|
|
} else {
|
|
BOOL shouldEchoReceivedData = lua_toboolean(L, 2);
|
|
[serialPort changeShouldEchoReceivedData:shouldEchoReceivedData];
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:stopBits([value]) -> number | serialPortObject
|
|
/// Method
|
|
/// Gets or sets the number of stop bits for the serial port.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional number to set the number of stop bits. It can be 1 or 2.
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns the number of stop bits as a number.
|
|
/// * The default value is 1.
|
|
static int serial_numberOfStopBits(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
NSUInteger numberOfStopBits = serialPort.numberOfStopBits;
|
|
[skin pushNSObject:@(numberOfStopBits)];
|
|
} else {
|
|
// Set:
|
|
int proposedNumberOfStopBits = (int)lua_tointeger(L, 2);
|
|
if (proposedNumberOfStopBits >= 1 && proposedNumberOfStopBits <= 2) {
|
|
[serialPort changeNumberOfStopBits:proposedNumberOfStopBits];
|
|
} else {
|
|
[skin logError:[NSString stringWithFormat:@"%s: Invalid number of stop bits Should be 1 or 2.", USERDATA_TAG]];
|
|
}
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:dataBits([value]) -> number | serialPortObject
|
|
/// Method
|
|
/// Gets or sets the number of data bits for the serial port.
|
|
///
|
|
/// Parameters:
|
|
/// * value - An optional number to set the number of data bits. It can be 5 to 8.
|
|
///
|
|
/// Returns:
|
|
/// * If a value is specified, then this method returns the serial port object. Otherwise this method returns the data bits as a number.
|
|
/// * The default value is 8.
|
|
static int serial_numberOfDataBits(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
if (lua_gettop(L) == 1) {
|
|
// Get:
|
|
NSUInteger numberOfStopBits = serialPort.numberOfDataBits;
|
|
[skin pushNSObject:@(numberOfStopBits)];
|
|
} else {
|
|
// Set:
|
|
int proposedNumberOfDataBits = (int)lua_tointeger(L, 2);
|
|
if (proposedNumberOfDataBits >= 5 && proposedNumberOfDataBits <= 8) {
|
|
[serialPort changeNumberOfDataBits:proposedNumberOfDataBits];
|
|
} else {
|
|
[skin logError:[NSString stringWithFormat:@"%s: Invalid number of data bits Should be 1 or 2.", USERDATA_TAG]];
|
|
}
|
|
lua_pushvalue(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:isOpen() -> boolean
|
|
/// Method
|
|
/// Gets whether or not a serial port is open.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * `true` if open, otherwise `false`.
|
|
static int serial_isOpen(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
BOOL isOpen = [serialPort isOpen];
|
|
lua_pushboolean(L, isOpen);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.serial:sendData(value) -> none
|
|
/// Method
|
|
/// Sends data via a serial port.
|
|
///
|
|
/// Parameters:
|
|
/// * value - A string of data to send.
|
|
///
|
|
/// Returns:
|
|
/// * None
|
|
static int serial_sendData(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TBREAK];
|
|
HSSerialPort *serialPort = [skin toNSObjectAtIndex:1];
|
|
NSData *dataToSend = [skin toNSObjectAtIndex:2 withOptions:LS_NSLuaStringAsDataOnly];
|
|
[serialPort sendData:dataToSend];
|
|
return 0;
|
|
}
|
|
|
|
// hs.serial.deviceCallback Manager:
|
|
HSSerialPort *watcherDeviceManager;
|
|
|
|
/// hs.serial.deviceCallback(callbackFn) -> none
|
|
/// Function
|
|
/// A callback that's triggered when a serial port is added or removed from the system.
|
|
///
|
|
/// Parameters:
|
|
/// * callbackFn - the callback function to trigger, or nil to remove the current callback
|
|
///
|
|
/// Returns:
|
|
/// * None
|
|
///
|
|
/// Notes:
|
|
/// * The callback function should expect 1 argument and should not return anything:
|
|
/// * `devices` - A table containing the names of any serial ports connected as strings.
|
|
static int serial_deviceCallback(lua_State *L) {
|
|
// Check Arguments:
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TFUNCTION | LS_TNIL, LS_TBREAK];
|
|
|
|
// Handle the case where the user wants to remove a callback:
|
|
if (lua_type(skin.L, 1) == LUA_TNIL) {
|
|
if (!watcherDeviceManager) {
|
|
// None of this was ever initialised, so this is a no-op
|
|
return 0;
|
|
}
|
|
|
|
// Remove any existing callback
|
|
if (watcherDeviceManager.deviceCallbackRef != LUA_NOREF) {
|
|
watcherDeviceManager.deviceCallbackRef = [skin luaUnref:refTable ref:watcherDeviceManager.deviceCallbackRef];
|
|
}
|
|
[watcherDeviceManager unwatchDevices];
|
|
watcherDeviceManager = nil;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Handle the case where the user wants to set a callback:
|
|
if (!watcherDeviceManager) {
|
|
watcherDeviceManager = [[HSSerialPort alloc] init];
|
|
watcherDeviceManager.lsCanary = [skin createGCCanary];
|
|
}
|
|
|
|
watcherDeviceManager.deviceCallbackRef = [skin luaRef:refTable atIndex:1];
|
|
[watcherDeviceManager watchDevices];
|
|
|
|
return 0;
|
|
}
|
|
|
|
#pragma mark - Lua<->NSObject Conversion Functions
|
|
|
|
// NOTE: These must not throw a Lua error to ensure LuaSkin can safely be used from Objective-C delegates and blocks:
|
|
|
|
static int pushHSSerialPort(lua_State *L, id obj) {
|
|
HSSerialPort *value = obj;
|
|
value.selfRefCount++;
|
|
void** valuePtr = lua_newuserdata(L, sizeof(HSSerialPort *));
|
|
*valuePtr = (__bridge_retained void *)value;
|
|
luaL_getmetatable(L, USERDATA_TAG);
|
|
lua_setmetatable(L, -2);
|
|
return 1;
|
|
}
|
|
|
|
static id toHSSerialPortFromLua(lua_State *L, int idx) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
HSSerialPort *value;
|
|
if (luaL_testudata(L, idx, USERDATA_TAG)) {
|
|
value = get_objectFromUserdata(__bridge HSSerialPort, L, idx, USERDATA_TAG);
|
|
} else {
|
|
[skin logError:[NSString stringWithFormat:@"expected %s object, found %s", USERDATA_TAG,
|
|
lua_typename(L, lua_type(L, idx))]];
|
|
}
|
|
return value;
|
|
}
|
|
|
|
#pragma mark - Hammerspoon/Lua Infrastructure
|
|
|
|
static int userdata_tostring(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
HSSerialPort *obj = [skin luaObjectAtIndex:1 toClass:"HSSerialPort"];
|
|
NSString *title = obj.portName;
|
|
BOOL isOpen = [obj isOpen];
|
|
NSString *connected = @"Connected";
|
|
if (!isOpen) {
|
|
connected = @"Disconnected";
|
|
}
|
|
[skin pushNSObject:[NSString stringWithFormat:@"%s: %@ - %@ (%p)", USERDATA_TAG, title, connected, lua_topointer(L, 1)]];
|
|
return 1;
|
|
}
|
|
|
|
static int userdata_eq(lua_State* L) {
|
|
// Can't get here if at least one of us isn't a userdata type, and we only care if both types are ours, so use luaL_testudata before the macro causes a Lua error:
|
|
if (luaL_testudata(L, 1, USERDATA_TAG) && luaL_testudata(L, 2, USERDATA_TAG)) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
HSSerialPort *obj1 = [skin luaObjectAtIndex:1 toClass:"HSSerialPort"];
|
|
HSSerialPort *obj2 = [skin luaObjectAtIndex:2 toClass:"HSSerialPort"];
|
|
lua_pushboolean(L, [obj1 isEqualTo:obj2]);
|
|
} else {
|
|
lua_pushboolean(L, NO);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// User Data Garbage Collection:
|
|
static int userdata_gc(lua_State* L) {
|
|
HSSerialPort *obj = get_objectFromUserdata(__bridge_transfer HSSerialPort, L, 1, USERDATA_TAG);
|
|
if (obj) {
|
|
obj.selfRefCount--;
|
|
if (obj.selfRefCount == 0) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
obj.callbackRef = [skin luaUnref:refTable ref:obj.callbackRef];
|
|
|
|
// Disconnect Callback:
|
|
if (obj.callbackToken != nil) {
|
|
[obj.serialPort close];
|
|
obj.callbackToken = nil;
|
|
}
|
|
obj = nil;
|
|
|
|
LSGCCanary tmplsCanary = obj.lsCanary;
|
|
[skin destroyGCCanary:&tmplsCanary];
|
|
obj.lsCanary = tmplsCanary;
|
|
}
|
|
}
|
|
|
|
// Remove the Metatable so future use of the variable in Lua won't think its valid
|
|
lua_pushnil(L);
|
|
lua_setmetatable(L, 1);
|
|
return 0;
|
|
}
|
|
|
|
// Metatable Garbage Collection:
|
|
static int meta_gc(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
|
|
if (watcherDeviceManager) {
|
|
if (watcherDeviceManager.deviceCallbackRef != LUA_NOREF) {
|
|
watcherDeviceManager.deviceCallbackRef = [skin luaUnref:refTable ref:watcherDeviceManager.deviceCallbackRef];
|
|
}
|
|
[watcherDeviceManager unwatchDevices];
|
|
watcherDeviceManager = nil;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Metatable for userdata objects:
|
|
static const luaL_Reg userdata_metaLib[] = {
|
|
{"name", serial_name},
|
|
{"path", serial_path},
|
|
{"open", serial_open},
|
|
{"close", serial_close},
|
|
{"baudRate", serial_baudRate},
|
|
{"sendData", serial_sendData},
|
|
{"parity", serial_parity},
|
|
{"isOpen", serial_isOpen},
|
|
{"callback", serial_callback},
|
|
{"stopBits", serial_numberOfStopBits},
|
|
{"dataBits", serial_numberOfDataBits},
|
|
{"shouldEchoReceivedData", serial_shouldEchoReceivedData},
|
|
{"usesRTSCTSFlowControl", serial_usesRTSCTSFlowControl},
|
|
{"usesDTRDSRFlowControl", serial_usesDTRDSRFlowControl},
|
|
{"usesDCDOutputFlowControl", serial_usesDCDOutputFlowControl},
|
|
{"__tostring", userdata_tostring},
|
|
{"__eq", userdata_eq},
|
|
{"__gc", userdata_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Functions for returned object when module loads:
|
|
static luaL_Reg moduleLib[] = {
|
|
{"newFromName", serial_newFromName},
|
|
{"newFromPath", serial_newFromPath},
|
|
{"availablePortNames", serial_availablePortNames},
|
|
{"availablePortPaths", serial_availablePortPaths},
|
|
{"availablePortDetails", serial_availablePortDetails},
|
|
{"deviceCallback", serial_deviceCallback},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Metatable for module:
|
|
static const luaL_Reg module_metaLib[] = {
|
|
{"__gc", meta_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Initalise Module:
|
|
int luaopen_hs_libserial(lua_State* L) {
|
|
// Register Module:
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
refTable = [skin registerLibraryWithObject:USERDATA_TAG
|
|
functions:moduleLib
|
|
metaFunctions:module_metaLib
|
|
objectFunctions:userdata_metaLib];
|
|
|
|
// Register Serial Port:
|
|
[skin registerPushNSHelper:pushHSSerialPort forClass:"HSSerialPort"];
|
|
[skin registerLuaObjectHelper:toHSSerialPortFromLua forClass:"HSSerialPort"
|
|
withUserdataMapping:USERDATA_TAG];
|
|
|
|
return 1;
|
|
}
|