#include "ofSerial.h"
#include "ofUtils.h"
#include "ofLog.h"
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
#include <sys/ioctl.h>
#include <getopt.h>
#include <dirent.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <algorithm>
#include <cstring>
using namespace std;
#ifdef TARGET_LINUX
#include <linux/serial.h>
#endif
#ifdef TARGET_WIN32
DEFINE_GUID (GUID_SERENUM_BUS_ENUMERATOR, 0x4D36E978, 0xE325,
0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18);
void ofSerial::enumerateWin32Ports(){
if(bPortsEnumerated == true){
return;
}
HDEVINFO hDevInfo = nullptr;
SP_DEVINFO_DATA DeviceInterfaceData;
DWORD dataType, actualSize = 0;
nPorts = 0;
hDevInfo = SetupDiGetClassDevs((struct _GUID *)&GUID_SERENUM_BUS_ENUMERATOR, 0, 0, DIGCF_PRESENT);
if(hDevInfo){
int i = 0;
unsigned char dataBuf[MAX_PATH + 1];
while(TRUE){
ZeroMemory(&DeviceInterfaceData, sizeof(DeviceInterfaceData));
DeviceInterfaceData.cbSize = sizeof(DeviceInterfaceData);
if(!SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInterfaceData)){
break;
}
if(SetupDiGetDeviceRegistryPropertyA(hDevInfo,
&DeviceInterfaceData,
SPDRP_FRIENDLYNAME,
&dataType,
dataBuf,
sizeof(dataBuf),
&actualSize)){
sprintf(portNamesFriendly[nPorts], "%s", dataBuf);
portNamesShort[nPorts][0] = 0;
char * begin = nullptr;
char * end = nullptr;
begin = strstr((char *)dataBuf, "COM");
if(begin){
end = strstr(begin, ")");
if(end){
*end = 0;
strcpy(portNamesShort[nPorts], begin);
}
if(nPorts++ > MAX_SERIAL_PORTS)
break;
}
}
i++;
}
}
SetupDiDestroyDeviceInfoList(hDevInfo);
bPortsEnumerated = false;
}
#endif
ofSerial::ofSerial(){
#ifdef TARGET_WIN32
nPorts = 0;
bPortsEnumerated = false;
portNamesShort = new char * [MAX_SERIAL_PORTS];
portNamesFriendly = new char * [MAX_SERIAL_PORTS];
for(int i = 0; i < MAX_SERIAL_PORTS; i++){
portNamesShort[i] = new char[10];
portNamesFriendly[i] = new char[MAX_PATH];
}
#else
fd = -1;
#endif
bHaveEnumeratedDevices = false;
bInited = false;
}
ofSerial::~ofSerial(){
close();
#ifdef TARGET_WIN32
nPorts = 0;
bPortsEnumerated = false;
for(int i = 0; i < MAX_SERIAL_PORTS; i++){
delete [] portNamesShort[i];
delete [] portNamesFriendly[i];
}
delete [] portNamesShort;
delete [] portNamesFriendly;
#endif
bInited = false;
}
#if defined( TARGET_OSX )
static bool isDeviceArduino( ofSerialDeviceInfo & A ){
return (strstr(A.getDeviceName().c_str(), "usbserial") != nullptr
|| strstr(A.getDeviceName().c_str(), "usbmodem") != nullptr);
}
#endif
void ofSerial::buildDeviceList(){
deviceType = "serial";
devices.clear();
vector <string> prefixMatch;
#ifdef TARGET_OSX
prefixMatch.push_back("cu.");
prefixMatch.push_back("tty.");
#endif
#ifdef TARGET_LINUX
prefixMatch.push_back("ttyACM");
prefixMatch.push_back("ttyS");
prefixMatch.push_back("ttyUSB");
prefixMatch.push_back("rfc");
#endif
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
ofDirectory dir("/dev");
int deviceCount = 0;
for(auto & entry: dir){
std::string deviceName = entry.getFileName();
for(auto & prefix: prefixMatch){
if(deviceName.size() > prefix.size()){
if(deviceName.substr(0, prefix.size()) == prefix.c_str()){
devices.push_back(ofSerialDeviceInfo("/dev/"+deviceName, deviceName, deviceCount));
deviceCount++;
break;
}
}
}
}
#endif
#ifdef TARGET_WIN32
enumerateWin32Ports();
ofLogNotice("ofSerial") << "found " << nPorts << " devices";
for(int i = 0; i < nPorts; i++){
devices.push_back(ofSerialDeviceInfo(string(portNamesShort[i]), string(portNamesFriendly[i]), i));
}
#endif
#if defined( TARGET_OSX )
partition(devices.begin(), devices.end(), isDeviceArduino);
int k = 0;
for(auto & device: devices){
device.deviceID = k++;
}
#endif
bHaveEnumeratedDevices = true;
}
void ofSerial::listDevices(){
buildDeviceList();
for(auto & device: devices){
ofLogNotice("ofSerial") << "[" << device.getDeviceID() << "] = "<< device.getDeviceName().c_str();
}
}
vector <ofSerialDeviceInfo> ofSerial::getDeviceList(){
buildDeviceList();
return devices;
}
void ofSerial::enumerateDevices(){
listDevices();
}
void ofSerial::close(){
#ifdef TARGET_WIN32
if(bInited){
SetCommTimeouts(hComm, &oldTimeout);
CloseHandle(hComm);
hComm = INVALID_HANDLE_VALUE;
bInited = false;
}
#else
if(bInited){
tcsetattr(fd, TCSANOW, &oldoptions);
::close(fd);
bInited = false;
}
#endif
}
bool ofSerial::setup(){
return setup(0, 9600);
}
bool ofSerial::setup(int deviceNumber, int baud){
buildDeviceList();
if(deviceNumber < (int)devices.size()){
return setup(devices[deviceNumber].devicePath, baud);
} else {
ofLogError("ofSerial") << "couldn't find device " << deviceNumber << ", only " << devices.size() << " devices found";
return false;
}
}
bool ofSerial::setup(string portName, int baud){
bInited = false;
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
if(portName.size() > 5 && portName.substr(0, 5) != "/dev/"){
portName = "/dev/" + portName;
}
ofLogNotice("ofSerial") << "opening " << portName << " @ " << baud << " bps";
fd = open(portName.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd == -1){
ofLogError("ofSerial") << "unable to open " << portName;
return false;
}
struct termios options;
tcgetattr(fd, &oldoptions);
options = oldoptions;
switch(baud){
case 300:
cfsetispeed(&options, B300);
cfsetospeed(&options, B300);
break;
case 1200:
cfsetispeed(&options, B1200);
cfsetospeed(&options, B1200);
break;
case 2400:
cfsetispeed(&options, B2400);
cfsetospeed(&options, B2400);
break;
case 4800:
cfsetispeed(&options, B4800);
cfsetospeed(&options, B4800);
break;
case 9600:
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
break;
case 14400:
cfsetispeed(&options, B14400);
cfsetospeed(&options, B14400);
break;
case 19200:
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
break;
case 28800:
cfsetispeed(&options, B28800);
cfsetospeed(&options, B28800);
break;
case 38400:
cfsetispeed(&options, B38400);
cfsetospeed(&options, B38400);
break;
case 57600:
cfsetispeed(&options, B57600);
cfsetospeed(&options, B57600);
break;
case 115200:
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
break;
case 230400:
cfsetispeed(&options, B230400);
cfsetospeed(&options, B230400);
break;
case 12000000:
cfsetispeed(&options, 12000000);
cfsetospeed(&options, 12000000);
break;
default:
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
ofLogError("ofSerial") << "setup(): cannot set " << baud << " bps, setting to 9600";
break;
}
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_iflag &= (tcflag_t) ~(INLCR | IGNCR | ICRNL | IGNBRK);
options.c_oflag &= (tcflag_t) ~(OPOST);
options.c_cflag |= CS8;
#if defined( TARGET_LINUX )
options.c_cflag |= CRTSCTS;
options.c_lflag &= ~(ICANON | ECHO | ISIG);
#endif
tcsetattr(fd, TCSANOW, &options);
#ifdef TARGET_LINUX
struct serial_struct kernel_serial_settings;
if (ioctl(fd, TIOCGSERIAL, &kernel_serial_settings) == 0) {
kernel_serial_settings.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &kernel_serial_settings);
}
#endif
bInited = true;
ofLogNotice("ofSerial") << "opened " << portName << " sucessfully @ " << baud << " bps";
return true;
#elif defined( TARGET_WIN32 )
char pn[sizeof(portName)];
int num;
if(sscanf(portName.c_str(), "COM%d", &num) == 1){
sprintf(pn, "\\\\.\\COM%d", num);
} else {
strncpy(pn, (const char *)portName.c_str(), sizeof(portName)-1);
}
hComm = CreateFileA(pn, GENERIC_READ|GENERIC_WRITE, 0, 0,
OPEN_EXISTING, 0, 0);
if(hComm == INVALID_HANDLE_VALUE){
ofLogError("ofSerial") << "setup(): unable to open " << portName;
return false;
}
COMMCONFIG cfg;
DWORD cfgSize;
WCHAR buf[80];
cfgSize = sizeof(cfg);
GetCommConfig(hComm, &cfg, &cfgSize);
int bps = baud;
swprintf(buf, L"baud=%d parity=N data=8 stop=1", bps);
if(!BuildCommDCBW(buf, &cfg.dcb)){
ofLogError("ofSerial") << "setup(): unable to build comm dcb, (" << buf << ")";
}
if(!SetCommState(hComm, &cfg.dcb)){
ofLogError("ofSerial") << "setup(): couldn't set comm state: " << cfg.dcb.BaudRate << " bps, xio " << cfg.dcb.fInX << "/" << cfg.dcb.fOutX;
}
COMMTIMEOUTS tOut;
GetCommTimeouts(hComm, &oldTimeout);
tOut = oldTimeout;
tOut.ReadIntervalTimeout = MAXDWORD;
tOut.ReadTotalTimeoutMultiplier = 0;
tOut.ReadTotalTimeoutConstant = 0;
SetCommTimeouts(hComm, &tOut);
bInited = true;
return true;
#else
ofLogError("ofSerial") << "not implemented in this platform";
return false;
#endif
}
long ofSerial::writeBytes(const char * buffer, size_t length){
if(!bInited){
ofLogError("ofSerial") << "writeBytes(): serial not inited";
return OF_SERIAL_ERROR;
}
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
size_t written=0;
fd_set wfds;
struct timeval tv;
while (written < length) {
auto n = write(fd, buffer + written, length - written);
if (n < 0 && (errno == EAGAIN || errno == EINTR)) n = 0;
if (n < 0) return OF_SERIAL_ERROR;
if (n > 0) {
written += n;
} else {
tv.tv_sec = 10;
tv.tv_usec = 0;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
n = select(fd+1, NULL, &wfds, NULL, &tv);
if (n < 0 && errno == EINTR) n = 1;
if (n <= 0) return OF_SERIAL_ERROR;
}
}
return written;
#elif defined(TARGET_WIN32)
DWORD written;
if(!WriteFile(hComm, buffer, length, &written,0)){
ofLogError("ofSerial") << "writeBytes(): couldn't write to port";
return OF_SERIAL_ERROR;
}
ofLogVerbose("ofSerial") << "wrote " << (int) written << " bytes";
return (int)written;
#else
return 0;
#endif
}
long ofSerial::writeBytes(const unsigned char * buffer, size_t length){
return writeBytes(reinterpret_cast<const char*>(buffer), length);
}
bool ofSerial::writeByte(char singleByte){
return writeByte(static_cast<unsigned char>(singleByte));
}
long ofSerial::writeBytes(const ofBuffer & buffer){
return writeBytes(buffer.getData(),buffer.size());
}
long ofSerial::readBytes(char * buffer, size_t length){
if (!bInited){
ofLogError("ofSerial") << "readBytes(): serial not inited";
return OF_SERIAL_ERROR;
}
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
auto nRead = read(fd, buffer, length);
if(nRead < 0){
if ( errno == EAGAIN )
return OF_SERIAL_NO_DATA;
ofLogError("ofSerial") << "readBytes(): couldn't read from port: " << errno << " " << strerror(errno);
return OF_SERIAL_ERROR;
}
return nRead;
#elif defined( TARGET_WIN32 )
DWORD nRead = 0;
if (!ReadFile(hComm, buffer, length, &nRead, 0)){
ofLogError("ofSerial") << "readBytes(): couldn't read from port";
return OF_SERIAL_ERROR;
}
return (int)nRead;
#else
ofLogError("ofSerial") << "not defined in this platform";
return -1;
#endif
}
long ofSerial::readBytes(unsigned char * buffer, size_t length){
return readBytes(reinterpret_cast<char*>(buffer), length);
}
long ofSerial::readBytes(ofBuffer & buffer, size_t length){
buffer.allocate(length);
return readBytes(buffer.getData(),length);
}
bool ofSerial::writeByte(unsigned char singleByte){
if (!bInited){
ofLogError("ofSerial") << "writeByte(): serial not inited";
return false;
}
return writeBytes(&singleByte,1) > 0;
}
int ofSerial::readByte(){
if(!bInited){
ofLogError("ofSerial") << "readByte(): serial not inited";
return OF_SERIAL_ERROR;
}
unsigned char tmpByte = 0;
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
int nRead = read(fd, &tmpByte, 1);
if(nRead < 0){
if ( errno == EAGAIN ){
return OF_SERIAL_NO_DATA;
}
ofLogError("ofSerial") << "readByte(): couldn't read from port: " << errno << " " << strerror(errno);
return OF_SERIAL_ERROR;
}
if(nRead == 0){
return OF_SERIAL_NO_DATA;
}
#elif defined( TARGET_WIN32 )
DWORD nRead;
if(!ReadFile(hComm, &tmpByte, 1, &nRead, 0)){
ofLogError("ofSerial") << "readByte(): couldn't read from port";
return OF_SERIAL_ERROR;
}
if(nRead == 0){
return OF_SERIAL_NO_DATA;
}
#else
ofLogError("ofSerial") << "not defined in this platform";
return OF_SERIAL_ERROR;
#endif
return tmpByte;
}
void ofSerial::flush(bool flushIn, bool flushOut){
if(!bInited){
ofLogError("ofSerial") << "flush(): serial not inited";
return;
}
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
int flushType = 0;
if(flushIn && flushOut) flushType = TCIOFLUSH;
else if(flushIn) flushType = TCIFLUSH;
else if(flushOut) flushType = TCOFLUSH;
else return;
tcflush(fd, flushType);
#elif defined( TARGET_WIN32 )
int flushType = 0;
if(flushIn && flushOut) flushType = PURGE_TXCLEAR | PURGE_RXCLEAR;
else if(flushIn) flushType = PURGE_RXCLEAR;
else if(flushOut) flushType = PURGE_TXCLEAR;
else return;
PurgeComm(hComm, flushType);
#endif
}
void ofSerial::drain(){
if(!bInited){
ofLogError("ofSerial") << "drain(): serial not inited";
return;
}
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
tcdrain(fd);
#endif
}
int ofSerial::available(){
if(!bInited){
ofLogError("ofSerial") << "available(): serial not inited";
return OF_SERIAL_ERROR;
}
int numBytes = 0;
#if defined( TARGET_OSX ) || defined( TARGET_LINUX )
ioctl(fd, FIONREAD, &numBytes);
#endif
#ifdef TARGET_WIN32
COMSTAT stat;
DWORD err;
if(hComm!=INVALID_HANDLE_VALUE){
if(!ClearCommError(hComm, &err, &stat)){
numBytes = 0;
} else {
numBytes = stat.cbInQue;
}
} else {
numBytes = 0;
}
#endif
return numBytes;
}
bool ofSerial::isInitialized() const{
return bInited;
}
Comments