ultimatepp/bazaar/Serial/SerialOSX.cpp
micio 1702b18a71 Bazaar/Serial : fix for OSX
git-svn-id: svn://ultimatepp.org/upp/trunk@10814 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2017-02-01 15:56:34 +00:00

575 lines
11 KiB
C++

#include "Serial.h"
#ifdef PLATFORM_OSX11
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <paths.h>
#include <termios.h>
#include <sysexits.h>
#include <sys/param.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/IOBSD.h>
namespace Upp
{
static bool __IsSymLink(const char *path)
{
struct stat stf;
lstat(path, &stf);
return S_ISLNK(stf.st_mode);
}
dword Serial::stdBauds[] =
{
0, 50, 75, 110, 134, 150,
200, 300, 600, 1200, 1800,
2400, 4800, 9600, 19200,
38400, 57600, 115200, 230400, 345600, 460800
};
int Serial::stdBaudsCount = sizeof(Serial::stdBauds) / sizeof(dword);
// constructor
Serial::Serial()
{
isError = false;
errCode = Ok;
fd = -1;
}
Serial::Serial(String const &port, dword speed, byte parity, byte bits, byte stopBits)
{
isError = false;
errCode = Ok;
fd = -1;
Open(port, speed, parity, bits, stopBits);
}
// destructor
Serial::~Serial()
{
Close();
}
// open the port
bool Serial::Open(String const &port, dword lSpeed, byte parity, byte bits, byte stopBits)
{
static int stdBaudCodes[] =
{
B0, B50, B75, B110, B134, B150,
B200, B300, B600, B1200, B1800,
B2400, B4800, B9600, B19200,
B38400, B57600, B115200, B230400
};
fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0)
{
fd = -1;
isError = true;
errCode = DeviceError;
return false;
}
if (ioctl(fd, TIOCEXCL) == -1)
{
// unable to get exclusive access to port
close(fd);
fd = -1;
isError = true;
errCode = DeviceError;
return false;
}
unsigned int ctl;
if (ioctl(fd, TIOCMGET, &ctl) < 0)
{
// unable to query serial ports signals
close(fd);
fd = -1;
isError = true;
errCode = DeviceError;
return false;
}
ctl &= ~(TIOCM_DTR | TIOCM_RTS);
if (ioctl(fd, TIOCMSET, &ctl) < 0)
{
// unable to control serial ports signals
close(fd);
fd = -1;
isError = true;
errCode = DeviceError;
return false;
}
// sets interface parameters
struct termios tty;
memset(&tty, 0, sizeof tty);
if (tcgetattr(fd, &tty) != 0)
{
Close();
isError = true;
errCode = DeviceError;
return false;
}
//// CFLAG /////
int idx = GetStandardBaudRates().Find(lSpeed);
dword speed;
if (idx < 0)
{
// custom speed
// @@ maybe add support later
Close();
isError = true;
errCode = InvalidSpeed;
return false;
}
else
{
// standard speed
speed = stdBaudCodes[idx];
cfsetospeed(&tty, speed);
cfsetispeed(&tty, speed);
}
// parity
tty.c_cflag &= ~(PARENB | PARODD);
switch (parity)
{
case ParityNone:
break;
case ParityEven:
tty.c_cflag |= PARENB;
break;
case ParityOdd:
tty.c_cflag |= PARENB | PARODD;
break;
default:
Close();
isError = true;
errCode = InvalidParity;
return false;
}
// set data size
switch (bits)
{
case 5:
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS5;
break;
case 6:
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS6;
break;
case 7:
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS7;
break;
case 8:
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
break;
default:
Close();
isError = true;
errCode = InvalidSize;
return false;
}
// stop bits
if (stopBits == 1)
tty.c_cflag &= ~CSTOPB;
else if (stopBits == 2)
tty.c_cflag |= CSTOPB;
else
{
Close();
isError = true;
errCode = InvalidStopBits;
return false;
}
// ignore modem controls, enable reading
tty.c_cflag |= (CLOCAL | CREAD);
// no rts/cts
tty.c_cflag &= ~CRTSCTS;
//// LFLAG /////
// no signaling chars, no echo, no canonical mode
tty.c_lflag = 0;
// tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//// IFLAG /////
// disable IGNBRK for mismatched speed tests; otherwise receive break as \000 chars
// disable break processing
tty.c_iflag &= ~IGNBRK;
// tty.c_iflag |= IGNBRK;
// DO NOT ignore carriage returns
tty.c_iflag &= ~IGNCR;
// DO NOT translate cr/lf
tty.c_iflag &= ~ICRNL;
// DO NOT translate cr
tty.c_iflag &= ~INLCR;
// shut off xon/xoff ctrl
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
//// OFLAG /////
// no remapping, no delays
tty.c_oflag = 0;
//// CONTROL CHARACTERS /////
// read doesn't block
tty.c_cc[VMIN] = 0;
// 0.5 seconds read timeout
tty.c_cc[VTIME] = 0;
/*
RLOG("CFLAGS : " << tty.c_cflag);
RLOG("LFLAGS : " << tty.c_lflag);
RLOG("IFLAGS : " << tty.c_iflag);
RLOG("OFLAGS : " << tty.c_oflag);
*/
if (tcsetattr(fd, TCSANOW, &tty) != 0)
{
Close();
isError = true;
errCode = DeviceError;
return false;
}
return true;
}
// close the port
void Serial::Close(void)
{
if (fd != -1)
close(fd);
fd = -1;
isError = false;
errCode = Ok;
}
// control DTR and RTS lines
bool Serial::SetDTR(bool on)
{
unsigned int ctl;
int r;
r = ioctl(fd, TIOCMGET, &ctl);
if (r < 0)
return false;
if (on)
ctl |= TIOCM_DTR;
else
ctl &= ~TIOCM_DTR;
r = ioctl(fd, TIOCMSET, &ctl);
if (r < 0)
return false;
return true;
}
bool Serial::SetRTS(bool on)
{
unsigned int ctl;
int r;
r = ioctl(fd, TIOCMGET, &ctl);
if (r < 0)
return false;
if (on)
ctl |= TIOCM_RTS;
else
ctl &= ~TIOCM_RTS;
r = ioctl(fd, TIOCMSET, &ctl);
if (r < 0)
return false;
return true;
}
// flush data
bool Serial::FlushInput(void)
{
if (fd == -1)
return false;
return !tcflush(fd, TCIFLUSH);
}
bool Serial::FlushOutput(void)
{
if (fd == -1)
return false;
return !tcflush(fd, TCOFLUSH);
}
bool Serial::FlushAll(void)
{
if (fd == -1)
return false;
return !tcflush(fd, TCIOFLUSH);
}
// check if data is available on serial port
int Serial::Avail(void)
{
if (fd == -1)
return false;
int bytes = 0;
ioctl(fd, FIONREAD, &bytes);
return bytes;
}
// read a single byte, block 'timeout' milliseconds
bool Serial::Read(byte &c, uint32_t timeout)
{
char buf[1];
uint32_t tim = msecs() + timeout;
for (;;)
{
if (!read(fd, buf, 1))
{
if ((uint32_t)msecs() > tim)
return false;
continue;
}
c = (byte)buf[0];
return true;
}
}
bool Serial::Read(char &c, uint32_t timeout)
{
char buf[1];
uint32_t tim = msecs() + timeout;
for (;;)
{
if (!read(fd, buf, 1))
{
if ((uint32_t)msecs() > tim)
return false;
continue;
}
c = buf[0];
return true;
}
}
// read data, requested amount, blocks 'timeout' milliseconds
// return number of bytes got
uint32_t Serial::Read(uint8_t *buf, uint32_t reqSize, uint32_t timeout)
{
if (!reqSize || !buf)
return 0;
uint32_t n;
uint32_t tim = msecs() + timeout;
uint32_t req = reqSize;
while (req)
{
n = read(fd, buf, req);
req -= n;
buf += n;
if (!req || msecs() > (int)tim)
break;
}
return reqSize - req;
}
// read data, requested amount, blocks 'timeout' milliseconds
// if reqSize == 0 just read all available data, waiting for 'timeout' if != 0
String Serial::Read(uint32_t reqSize, uint32_t timeout)
{
char buf[1001];
buf[1000] = 0;
String data;
size_t n;
uint32_t tim = msecs() + timeout;
if (reqSize)
{
n = min(reqSize, (uint32_t)1000);
while (reqSize)
{
n = read(fd, buf, n);
if (!n)
{
if ((uint32_t)msecs() > tim)
break;
n = min(reqSize, (uint32_t)1000);
continue;
}
tim = msecs() + timeout;
if (n)
{
reqSize -= n;
data.Cat(buf, n);
}
n = min(reqSize, (uint32_t)1000);
}
}
else
{
for (;;)
{
n = read(fd, buf, 1000);
if (!n)
{
if ((uint32_t)msecs() > tim)
break;
continue;
}
tim = msecs() + timeout;
if (n)
data.Cat(buf, n);
}
}
return data;
}
// write a single byte
bool Serial::Write(char c, uint32_t timeout)
{
if (!timeout)
return write(fd, &c, 1);
uint32_t tim = msecs() + timeout;
for (;;)
{
if (write(fd, &c, 1) == 1)
return true;
if ((uint32_t)msecs() > tim)
break;
}
return false;
}
// write buffer
bool Serial::Write(uint8_t const *buf, uint32_t len, uint32_t timeout)
{
if (!timeout)
return (write(fd, buf, len) == len);
uint32_t tim = msecs() + timeout;
const uint8_t *dPos = buf;
for (;;)
{
int written = write(fd, dPos, len);
if (written == len)
return true;
if (written >= 0)
{
dPos += written;
len -= written;
continue;
}
if ((uint32_t)msecs() >= tim)
break;
}
return false;
}
// writes string
bool Serial::Write(String const &data, uint32_t timeout)
{
return Write((const uint8_t *)~data, data.GetCount(), timeout);
}
// check if opened
bool Serial::IsOpened(void) const
{
return fd != -1;
}
static void macos_ports(io_iterator_t *PortIterator, ArrayMap<String, String> &list)
{
io_object_t modemService;
CFTypeRef nameCFstring;
char s[MAXPATHLEN];
while ((modemService = IOIteratorNext(*PortIterator)))
{
nameCFstring = IORegistryEntryCreateCFProperty(modemService, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
if (nameCFstring)
{
if (CFStringGetCString((const __CFString *)nameCFstring, s, sizeof(s), kCFStringEncodingASCII))
list.Add(s, "");
CFRelease(nameCFstring);
}
IOObjectRelease(modemService);
}
}
// get a list of connected serial ports
// for old-style serials, check if something is connected
// for usb ones, return also a description
ArrayMap<String, String> Serial::GetSerialPorts(void)
{
ArrayMap<String, String> res;
mach_port_t masterPort;
CFMutableDictionaryRef classesToMatch;
io_iterator_t serialPortIterator;
if (IOMasterPort(0, &masterPort) != KERN_SUCCESS)
return res;
// a usb-serial adaptor is usually considered a "modem",
// especially when it implements the CDC class spec
classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
if (!classesToMatch)
return res;
CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType));
if (IOServiceGetMatchingServices(masterPort, classesToMatch, &serialPortIterator) != KERN_SUCCESS)
return res;
macos_ports(&serialPortIterator, res);
IOObjectRelease(serialPortIterator);
// but it might be considered a "rs232 port", so repeat this search for rs232 ports
classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
if (!classesToMatch)
return res;
CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type));
if (IOServiceGetMatchingServices(masterPort, classesToMatch, &serialPortIterator) != KERN_SUCCESS)
return res;
macos_ports(&serialPortIterator, res);
IOObjectRelease(serialPortIterator);
return res;
}
} // END_UPP_NAMESPACE
#endif