Initial commit

This commit is contained in:
Paul Hollinsky
2018-09-10 20:28:29 -04:00
commit e2e5017331
133 changed files with 24597 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
#ifndef __DYNAMICLIB_H_
#define __DYNAMICLIB_H_
#if defined _WIN32
#include "platform/windows/include/dynamiclib.h"
#elif defined __linux__
#include "platform/linux/include/dynamiclib.h"
#else
#warning "This platform is not supported by the dynamic library driver"
#endif
#endif
+14
View File
@@ -0,0 +1,14 @@
#ifndef __FTDI_H_
#define __FTDI_H_
#define INTREPID_USB_VENDOR_ID (0x093c)
#if defined _WIN32
#include "platform/windows/include/ftdi.h"
#elif defined __linux__
#include "platform/linux/include/ftdi.h"
#else
#warning "This platform is not supported by the FTDI driver"
#endif
#endif
+10
View File
@@ -0,0 +1,10 @@
#ifndef __REGISTRY_H_
#define __REGISTRY_H_
#if defined _WIN32
#include "platform/windows/include/registry.h"
#else
#warning "This platform is not supported by the registry driver"
#endif
#endif
+14
View File
@@ -0,0 +1,14 @@
#ifndef __STM32_H_
#define __STM32_H_
#define INTREPID_USB_VENDOR_ID (0x093c)
#if defined _WIN32
#include "platform/windows/include/stm32.h"
#elif defined __linux__
#include "platform/linux/include/stm32.h"
#else
#warning "This platform is not supported by the STM32 driver"
#endif
#endif
+125
View File
@@ -0,0 +1,125 @@
#include "platform/linux/include/ftdi.h"
#include "platform/include/ftdi.h"
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <memory>
using namespace icsneo;
// Instantiate static variables
neodevice_handle_t FTDI::handleCounter = 1;
Ftdi::Context FTDI::context;
std::vector<FTDI::FTDIDevice> FTDI::searchResultDevices;
/* Theory: Ftdi::List::find_all gives us back Ftdi::Context objects, but these can't be passed
* back and forth with C nicely. So we wrap the Ftdi::Context objects in FTDIDevice classes which
* will give it a nice neodevice_handle_t handle that we can reference it by. These FTDIDevice objects are
* stored in searchResultDevices, and then moved into the instantiated FTDI class by the constructor.
*/
std::vector<neodevice_t> FTDI::FindByProduct(int product) {
constexpr size_t deviceSerialBufferLength = sizeof(device.serial);
std::vector<neodevice_t> found;
auto devlist = std::unique_ptr<Ftdi::List>(Ftdi::List::find_all(context, INTREPID_USB_VENDOR_ID, product));
searchResultDevices.clear();
for(auto it = devlist->begin(); it != devlist->end(); it++)
searchResultDevices.push_back(*it); // The upconversion to FTDIDevice will assign a handle
for(auto& dev : searchResultDevices) {
neodevice_t d;
auto& serial = dev.serial();
strncpy(d.serial, serial.c_str(), deviceSerialBufferLength - 1);
d.serial[deviceSerialBufferLength - 1] = '\0'; // strncpy does not write a null terminator if serial is too long
d.handle = dev.handle;
found.push_back(d);
}
return found;
}
bool FTDI::IsHandleValid(neodevice_handle_t handle) {
for(auto& dev : searchResultDevices) {
if(dev.handle != handle)
continue;
return true;
}
return false;
}
bool FTDI::GetDeviceForHandle(neodevice_handle_t handle, FTDIDevice& device) {
for(auto& dev : searchResultDevices) {
if(dev.handle != handle)
continue;
device = dev;
return true;
}
return false;
}
FTDI::FTDI(neodevice_t& forDevice) : device(forDevice) {
openable = GetDeviceForHandle(forDevice.handle, ftdiDevice);
}
bool FTDI::open() {
if(isOpen() || !openable)
return false;
if(ftdiDevice.open())
return false;
ftdiDevice.set_usb_read_timeout(100);
ftdiDevice.set_usb_write_timeout(1000);
ftdiDevice.reset();
ftdiDevice.set_baud_rate(500000);
ftdiDevice.flush();
// Create threads
closing = false;
readThread = std::thread(&FTDI::readTask, this);
writeThread = std::thread(&FTDI::writeTask, this);
return true;
}
bool FTDI::close() {
if(!isOpen())
return false;
closing = true;
if(readThread.joinable())
readThread.join();
if(writeThread.joinable())
writeThread.join();
ftdiDevice.set_dtr(false);
if(ftdiDevice.close())
return false;
return true;
}
void FTDI::readTask() {
constexpr size_t READ_BUFFER_SIZE = 8;
uint8_t readbuf[READ_BUFFER_SIZE];
while(!closing) {
auto readBytes = ftdiDevice.read(readbuf, READ_BUFFER_SIZE);
if(readBytes > 0)
readQueue.enqueue_bulk(readbuf, readBytes);
}
}
void FTDI::writeTask() {
WriteOperation writeOp;
while(!closing) {
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
continue;
ftdiDevice.write(writeOp.bytes.data(), (int)writeOp.bytes.size());
}
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef __DYNAMICLIB_H_LINUX_
#define __DYNAMICLIB_H_LINUX_
#include <dlfcn.h>
// Nothing special is needed to export
#define DLLExport
// #ifndef ICSNEO_NO_AUTO_DESTRUCT
// #define ICSNEO_DESTRUCTOR __attribute__((destructor));
// #else
#define ICSNEO_DESTRUCTOR
// #endif
#define icsneoDynamicLibraryLoad() dlopen("/media/paulywog/Windows 10/Users/phollinsky/Code/icsneonext/build/libicsneoc.so", RTLD_LAZY)
#define icsneoDynamicLibraryGetFunction(handle, func) dlsym(handle, func)
#define icsneoDynamicLibraryClose(handle) (dlclose(handle) == 0)
#endif
+51
View File
@@ -0,0 +1,51 @@
#ifndef __FTDI_H_LINUX_
#define __FTDI_H_LINUX_
#include <vector>
#include <memory>
#include <string>
#include <atomic>
#include <ftdi.hpp>
#include "device/include/neodevice.h"
#include "communication/include/icommunication.h"
#include "third-party/concurrentqueue/blockingconcurrentqueue.h"
namespace icsneo {
class FTDI : public ICommunication {
public:
static constexpr neodevice_handle_t INVALID_HANDLE = 0x7fffffff; // int32_t max value
static std::vector<neodevice_t> FindByProduct(int product);
static bool IsHandleValid(neodevice_handle_t handle);
FTDI(neodevice_t& forDevice);
~FTDI() { close(); }
bool open();
bool close();
bool isOpen() { return ftdiDevice.is_open(); }
private:
static Ftdi::Context context;
static neodevice_handle_t handleCounter;
class FTDIDevice : public Ftdi::Context {
public:
FTDIDevice() {}
FTDIDevice(const Ftdi::Context &x) : Ftdi::Context(x) {
handle = handleCounter++;
}
neodevice_handle_t handle = INVALID_HANDLE;
};
static std::vector<FTDIDevice> searchResultDevices;
static bool GetDeviceForHandle(neodevice_handle_t handle, FTDIDevice& device);
void readTask();
void writeTask();
bool openable; // Set to false in the constructor if the object has not been found in searchResultDevices
neodevice_t& device;
FTDIDevice ftdiDevice;
};
};
#endif
+31
View File
@@ -0,0 +1,31 @@
#ifndef __STM32_LINUX_H_
#define __STM32_LINUX_H_
#include "communication/include/icommunication.h"
#include "device/include/neodevice.h"
#include <chrono>
#include <stdint.h>
namespace icsneo {
class STM32 : public ICommunication {
public:
STM32(neodevice_t& forDevice) : device(forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product);
bool open();
bool isOpen();
bool close();
private:
neodevice_t& device;
int fd = -1;
static constexpr neodevice_handle_t HANDLE_OFFSET = 10;
void readTask();
void writeTask();
};
};
#endif
+283
View File
@@ -0,0 +1,283 @@
#include "platform/include/stm32.h"
#include <dirent.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <fstream>
#include <map>
#include <algorithm>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
using namespace icsneo;
class Directory {
public:
class Listing {
public:
Listing(std::string newName, uint8_t newType) : name(newName), type(newType) {}
const std::string& getName() const { return name; }
uint8_t getType() const { return type; }
private:
std::string name;
uint8_t type;
};
Directory(std::string directory) {
dir = opendir(directory.c_str());
}
~Directory() {
if(openedSuccessfully())
closedir(dir);
dir = nullptr;
}
bool openedSuccessfully() { return dir != nullptr; }
std::vector<Listing> ls() {
std::vector<Listing> results;
struct dirent* entry;
while((entry = readdir(dir)) != nullptr) {
std::string name = entry->d_name;
if(name != "." && name != "..") // Ignore parent and self
results.emplace_back(name, entry->d_type);
}
return results;
}
private:
DIR* dir;
};
class USBSerialGetter {
public:
USBSerialGetter(std::string usbid) {
std::stringstream ss;
auto colonpos = usbid.find(":");
if(colonpos == std::string::npos) {
succeeded = false;
return;
}
ss << "/sys/bus/usb/devices/" << usbid.substr(0, colonpos) << "/serial";
try {
std::ifstream reader(ss.str());
std::getline(reader, serial);
} catch(...) {
succeeded = false;
return;
}
succeeded = true;
}
bool success() const { return succeeded; }
const std::string& getSerial() const { return serial; }
private:
bool succeeded;
std::string serial;
};
std::vector<neodevice_t> STM32::FindByProduct(int product) {
std::vector<neodevice_t> found;
Directory directory("/sys/bus/usb/drivers/cdc_acm"); // Query the STM32 driver
if(!directory.openedSuccessfully())
return found;
std::vector<std::string> foundusbs;
for(auto& entry : directory.ls()) {
/* This directory will have directories (links) for all devices using the cdc_acm driver (as STM32 devices do)
* There will also be other files and directories providing information about the driver in here. We want to ignore them.
* Devices will be named like "7-2:1.0" where 7 is the enumeration for the USB controller, 2 is the device enumeration on
* that specific controller (will change if the device is unplugged and replugged), 1 is the device itself and 0 is
* enumeration for different services provided by the device. We're looking for the service that provides TTY.
* For now we find the directories with a digit for the first character, these are likely to be our USB devices.
*/
if(isdigit(entry.getName()[0]) && entry.getType() == DT_LNK)
foundusbs.emplace_back(entry.getName());
}
// Pair the USB and TTY if found
std::map<std::string, std::string> foundttys;
for(auto& usb : foundusbs) {
std::stringstream ss;
ss << "/sys/bus/usb/drivers/cdc_acm/" << usb << "/tty";
Directory devicedir(ss.str());
if(!devicedir.openedSuccessfully()) // The tty directory doesn't exist, because this is not the tty service we want
continue;
auto listing = devicedir.ls();
if(listing.size() != 1) // We either got no serial ports or multiple, either way no good
continue;
foundttys.insert(std::make_pair(usb, listing[0].getName()));
}
// We're going to remove from the map if this is not the product we're looking for
for(auto iter = foundttys.begin(); iter != foundttys.end(); ) {
const auto& dev = *iter;
const std::string matchString = "PRODUCT=";
std::stringstream ss;
ss << "/sys/class/tty/" << dev.second << "/device/uevent"; // Read the uevent file, which contains should have a line like "PRODUCT=93c/1101/100"
std::ifstream fs(ss.str());
std::string productLine;
size_t pos = std::string::npos;
do {
std::getline(fs, productLine, '\n');
} while(((pos = productLine.find(matchString)) == std::string::npos) && !fs.eof());
if(pos != 0) { // We did not find a product line... weird
iter = foundttys.erase(iter); // Remove the element, this also moves iter forward for us
continue;
}
size_t firstSlashPos = productLine.find('/', matchString.length());
if(firstSlashPos == std::string::npos) {
iter = foundttys.erase(iter);
continue;
}
size_t pidpos = firstSlashPos + 1;
std::string vidstr = productLine.substr(matchString.length(), firstSlashPos - matchString.length());
std::string pidstr = productLine.substr(pidpos, productLine.find('/', pidpos) - pidpos); // In hex like "1101" or "93c"
uint16_t vid, pid;
try {
vid = (uint16_t)std::stoul(vidstr, nullptr, 16);
pid = (uint16_t)std::stoul(pidstr, nullptr, 16);
} catch(...) {
iter = foundttys.erase(iter); // We could not parse the numbers
continue;
}
if(vid != INTREPID_USB_VENDOR_ID || pid != product) {
iter = foundttys.erase(iter); // Not the right VID or PID, remove
continue;
}
iter++; // If the loop ends without erasing the iter from the map, the item is good
}
// At this point, foundttys contains the the devices we want
// Get the serial number, create the neodevice_t
for(auto& dev : foundttys) {
neodevice_t device;
USBSerialGetter getter(dev.first);
if(!getter.success())
continue; // Failure, could not get serial number
// In ttyACM0, we want the i to be the first character of the number
size_t i;
for(i = 0; i < dev.second.length(); i++) {
if(isdigit(dev.second[i]))
break;
}
// Now we try to parse the number so we have a handle for later
try {
device.handle = (neodevice_handle_t)std::stoul(dev.second.substr(i));
/* The TTY numbering starts at zero, but we want to keep zero for an undefined
* handle, so add a constant, and we'll subtract that constant in the open function.
*/
device.handle += HANDLE_OFFSET;
} catch(...) {
continue; // Somehow this failed, have to toss the device
}
device.serial[getter.getSerial().copy(device.serial, sizeof(device.serial)-1)] = '\0';
found.push_back(device); // Finally, add device to search results
}
return found;
}
bool STM32::open() {
std::stringstream ss;
ss << "/dev/ttyACM" << (int)(device.handle - HANDLE_OFFSET);
fd = ::open(ss.str().c_str(), O_RDWR | O_NOCTTY | O_SYNC);
if(!isOpen()) {
std::cout << "Open of " << ss.str().c_str() << " failed with " << strerror(errno) << ' ';
return false;
}
struct termios tty;
if(tcgetattr(fd, &tty) < 0) {
close();
return false;
}
cfsetspeed(&tty, B500000); // Set speed to 500kbaud
tty.c_cflag |= (CLOCAL | CREAD); // Ignore modem controls
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; // 8-bit characters
tty.c_cflag &= ~PARENB; // No parity bit
tty.c_cflag &= ~CSTOPB; // One stop bit
tty.c_cflag &= ~CRTSCTS; // No hardware flow control
// Non-canonical mode
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;
// Fetch bytes as they become available
// See http://man7.org/linux/man-pages/man3/termios.3.html
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 1; // 100ms timeout (1 decisecond, what?)
if(tcsetattr(fd, TCSAFLUSH, &tty) != 0) { // Flushes input and output buffers as well as setting settings
close();
return false;
}
// Create threads
readThread = std::thread(&STM32::readTask, this);
writeThread = std::thread(&STM32::writeTask, this);
return true;
}
bool STM32::isOpen() {
return fd >= 0; // Negative fd indicates error or not opened yet
}
bool STM32::close() {
if(!isOpen())
return false;
closing = true;
if(readThread.joinable())
readThread.join();
if(writeThread.joinable())
writeThread.join();
int ret = ::close(fd);
fd = -1;
return ret == 0;
}
void STM32::readTask() {
constexpr size_t READ_BUFFER_SIZE = 8;
uint8_t readbuf[READ_BUFFER_SIZE];
while(!closing) {
auto bytesRead = ::read(fd, readbuf, READ_BUFFER_SIZE);
if(bytesRead > 0)
readQueue.enqueue_bulk(readbuf, bytesRead);
}
}
void STM32::writeTask() {
WriteOperation writeOp;
while(!closing) {
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
continue;
const auto writeSize = writeOp.bytes.size();
int actualWritten = ::write(fd, writeOp.bytes.data(), writeSize);
if(actualWritten != writeSize)
std::cout << "Failure to write " << writeSize << " bytes, wrote " << actualWritten << std::endl;
}
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef __DYNAMICLIB_H_WINDOWS_
#define __DYNAMICLIB_H_WINDOWS_
#include <Windows.h>
#ifdef ICSNEOC_MAKEDLL
#define DLLExport __declspec(dllexport)
#else
#define DLLExport __declspec(dllimport)
#endif
// MSVC does not have the ability to specify a destructor
#define ICSNEO_DESTRUCTOR
#define icsneoDynamicLibraryLoad() LoadLibrary(L"C:\\Users\\Phollinsky\\Code\\icsneonext\\build\\icsneoc.dll")
#define icsneoDynamicLibraryGetFunction(handle, func) GetProcAddress((HMODULE) handle, func)
#define icsneoDynamicLibraryClose(handle) FreeLibrary((HMODULE) handle)
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef __FTDI_WINDOWS_H_
#define __FTDI_WINDOWS_H_
#include "platform/windows/include/vcp.h"
namespace icsneo {
class FTDI : public VCP {
public:
FTDI(neodevice_t& forDevice) : VCP(forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, L"serenum"); }
};
};
#endif
+33
View File
@@ -0,0 +1,33 @@
#ifndef __REGISTRY_H_WINDOWS_
#define __REGISTRY_H_WINDOWS_
#include <Windows.h>
#include <string>
namespace icsneo {
class Registry {
public:
// Get string value
static bool Get(std::wstring path, std::wstring key, std::wstring& value);
static bool Get(std::string path, std::string key, std::string& value);
// Get DWORD value
static bool Get(std::wstring path, std::wstring key, uint32_t& value);
static bool Get(std::string path, std::string key, uint32_t& value);
private:
class Key {
public:
Key(std::wstring path, bool readwrite = false);
~Key();
HKEY GetKey() { return key; }
bool IsOpen() { return key != nullptr; }
private:
HKEY key;
};
};
};
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef __STM32_WINDOWS_H_
#define __STM32_WINDOWS_H_
#include "platform/windows/include/vcp.h"
namespace icsneo {
class STM32 : public VCP {
public:
STM32(neodevice_t& forDevice) : VCP(forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, L"usbser"); }
};
};
#endif
+48
View File
@@ -0,0 +1,48 @@
#ifndef __VCP_H_WINDOWS_
#define __VCP_H_WINDOWS_
#include <vector>
#include <string>
#include <thread>
#include <atomic>
#include <chrono>
#include <Windows.h>
#include "device/include/neodevice.h"
#include "communication/include/icommunication.h"
namespace icsneo {
// Virtual COM Port Communication
class VCP : public ICommunication {
public:
static std::vector<neodevice_t> FindByProduct(int product, wchar_t* driverName);
static bool IsHandleValid(neodevice_handle_t handle);
typedef void(*fn_boolCallback)(bool success);
VCP(neodevice_t& forDevice) : device(forDevice) {
overlappedRead.hEvent = INVALID_HANDLE_VALUE;
overlappedWrite.hEvent = INVALID_HANDLE_VALUE;
overlappedWait.hEvent = INVALID_HANDLE_VALUE;
}
~VCP() { close(); }
bool open() { return open(false); }
void openAsync(fn_boolCallback callback);
bool close();
bool isOpen() { return handle != INVALID_HANDLE_VALUE; }
private:
bool open(bool fromAsync);
bool opening = false;
neodevice_t& device;
HANDLE handle = INVALID_HANDLE_VALUE;
OVERLAPPED overlappedRead = {};
OVERLAPPED overlappedWrite = {};
OVERLAPPED overlappedWait = {};
std::vector<std::shared_ptr<std::thread>> threads;
void readTask();
void writeTask();
};
};
#endif
+68
View File
@@ -0,0 +1,68 @@
#include "platform/windows/include/registry.h"
#include <codecvt>
#include <vector>
using namespace icsneo;
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
Registry::Key::Key(std::wstring path, bool readwrite) {
DWORD dwDisposition;
if(readwrite)
RegCreateKeyExW(HKEY_LOCAL_MACHINE, path.c_str(), 0, nullptr, 0, KEY_QUERY_VALUE | KEY_WRITE, nullptr, &key, &dwDisposition);
else
RegOpenKeyExW(HKEY_LOCAL_MACHINE, path.c_str(), 0, KEY_READ, &key);
}
Registry::Key::~Key() {
if(IsOpen())
RegCloseKey(key);
}
bool Registry::Get(std::wstring path, std::wstring key, std::wstring& value) {
Key regKey(path);
if(!regKey.IsOpen())
return false;
// Query for the type and size of the data
DWORD type, size;
auto ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)nullptr, &size);
if(ret != ERROR_SUCCESS)
return false;
// Query for the data itself
std::vector<wchar_t> data(size / 2 + 1);
DWORD bytesRead = size; // We want to read up to the size we got earlier
ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)data.data(), &bytesRead);
if(ret != ERROR_SUCCESS)
return false;
value = data.data();
return true;
}
bool Registry::Get(std::string path, std::string key, std::string& value) {
std::wstring wvalue;
bool ret = Get(converter.from_bytes(path), converter.from_bytes(key), wvalue);
value = converter.to_bytes(wvalue);
return ret;
}
bool Registry::Get(std::wstring path, std::wstring key, uint32_t& value) {
Key regKey(path);
if(!regKey.IsOpen())
return false;
// Query for the data
DWORD type, size, kvalue;
auto ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)&kvalue, &size);
if(ret != ERROR_SUCCESS || type != REG_DWORD)
return false;
value = kvalue;
return true;
}
bool Registry::Get(std::string path, std::string key, uint32_t& value) {
return Get(converter.from_bytes(path), converter.from_bytes(key), value);
}
+329
View File
@@ -0,0 +1,329 @@
#include "platform/windows/include/ftdi.h"
#include "platform/include/ftdi.h"
#include "platform/include/registry.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cwctype>
#include <algorithm>
#include <codecvt>
#include <limits>
#include <stdio.h>
using namespace icsneo;
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
static const std::wstring DRIVER_SERVICES_REG_KEY = L"SYSTEM\\CurrentControlSet\\services\\";
static const std::wstring ALL_ENUM_REG_KEY = L"SYSTEM\\CurrentControlSet\\Enum\\";
static constexpr unsigned int RETRY_TIMES = 5;
static constexpr unsigned int RETRY_DELAY = 50;
std::vector<neodevice_t> VCP::FindByProduct(int product, wchar_t* driverName) {
std::vector<neodevice_t> found;
std::wstringstream regss;
regss << DRIVER_SERVICES_REG_KEY << driverName << L"\\Enum\\";
std::wstring driverEnumRegKey = regss.str();
uint32_t deviceCount = 0;
if(!Registry::Get(driverEnumRegKey, L"Count", deviceCount)) {
return found;
}
for(uint32_t i = 0; i < deviceCount; i++) {
neodevice_t device = {};
// First we want to look at what devices FTDI is enumerating (inside driverEnumRegKey)
// The entry for a ValueCAN 3 with SN 138635 looks like "FTDIBUS\VID_093C+PID_0601+138635A\0000"
// The entry for a ValueCAN 4 with SN V20227 looks like "USB\VID_093C&PID_1101\V20227"
std::wstringstream ss;
ss << i;
std::wstring entry;
if(!Registry::Get(driverEnumRegKey, ss.str(), entry))
continue;
std::transform(entry.begin(), entry.end(), entry.begin(), std::towupper);
std::wstringstream vss;
vss << "VID_" << std::setfill(L'0') << std::setw(4) << std::uppercase << std::hex << INTREPID_USB_VENDOR_ID; // Intrepid Vendor ID
if(entry.find(vss.str()) == std::wstring::npos)
continue;
std::wstringstream pss;
pss << "PID_" << std::setfill(L'0') << std::setw(4) << std::uppercase << std::hex << product;
auto pidpos = entry.find(pss.str());
if(pidpos == std::wstring::npos)
continue;
// Okay, this is a device we want
// Get the serial number
auto startchar = entry.find(L"+", pidpos + 1);
if(startchar == std::wstring::npos)
startchar = entry.find(L"\\", pidpos + 1);
bool conversionError = false;
int sn = 0;
try {
sn = std::stoi(entry.substr(startchar + 1));
} catch(...) {
conversionError = true;
}
std::wstringstream oss;
if(!sn || conversionError) {
// This is a device with characters in the serial number
oss << entry.substr(startchar + 1, 6);
} else {
oss << sn;
}
strcpy_s(device.serial, sizeof(device.serial), converter.to_bytes(oss.str()).c_str());
// Serial number is saved, we want the COM port number now
// This will be stored under ALL_ENUM_REG_KEY\entry\Device Parameters\PortName (entry from the FTDI_ENUM)
std::wstringstream dpss;
dpss << ALL_ENUM_REG_KEY << entry << L"\\Device Parameters";
std::wstring port;
Registry::Get(dpss.str(), L"PortName", port); // TODO If error do something else (Plasma maybe?)
std::transform(port.begin(), port.end(), port.begin(), std::towupper);
auto compos = port.find(L"COM");
device.handle = 0;
if(compos != std::wstring::npos) {
try {
device.handle = std::stoi(port.substr(compos + 3));
} catch(...) {} // In case of this, or any other error, handle has already been initialized to 0
}
found.push_back(device);
}
return found;
}
bool VCP::IsHandleValid(neodevice_handle_t handle) {
if(handle < 1)
return false;
if(handle > 256) // Windows default max COM port is COM256
return false; // TODO Enumerate subkeys of HKLM\HARDWARE\DEVICEMAP\SERIALCOMM as a user might have more serial ports somehow
return true;
}
bool VCP::open(bool fromAsync) {
if(isOpen() || (!fromAsync && opening))
return false;
if(!IsHandleValid(device.handle))
return false;
opening = true;
std::wstringstream comss;
comss << L"\\\\.\\COM" << device.handle;
// We're going to attempt to open 5 (RETRY_TIMES) times in a row
for(int i = 0; !isOpen() && i < RETRY_TIMES; i++) {
handle = CreateFileW(comss.str().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
if(GetLastError() == ERROR_SUCCESS)
break; // We have the file handle
std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_DELAY));
}
opening = false;
if(!isOpen())
return false;
// Set the timeouts
COMMTIMEOUTS timeouts;
if(!GetCommTimeouts(handle, &timeouts)) {
close();
return false;
}
timeouts.WriteTotalTimeoutConstant = 10000;
timeouts.WriteTotalTimeoutMultiplier = 0;
if(!SetCommTimeouts(handle, &timeouts)) {
close();
return false;
}
// Set the COM state
DCB comstate;
if(!GetCommState(handle, &comstate)) {
close();
return false;
}
comstate.BaudRate = 115200;
comstate.ByteSize = 8;
comstate.Parity = NOPARITY;
comstate.StopBits = 0;
comstate.fDtrControl = DTR_CONTROL_ENABLE;
comstate.fRtsControl = RTS_CONTROL_ENABLE;
if(!SetCommState(handle, &comstate)) {
close();
return false;
}
PurgeComm(handle, PURGE_RXCLEAR);
// Set up events so that overlapped IO can work with them
overlappedRead.hEvent = CreateEvent(nullptr, false, false, nullptr);
overlappedWrite.hEvent = CreateEvent(nullptr, false, false, nullptr);
overlappedWait.hEvent = CreateEvent(nullptr, true, false, nullptr);
if (overlappedRead.hEvent == nullptr || overlappedWrite.hEvent == nullptr || overlappedWait.hEvent == nullptr) {
close();
return false;
}
// Set up event so that we will satisfy overlappedWait when a character comes in
if(!SetCommMask(handle, EV_RXCHAR)) {
close();
return false;
}
// TODO Set up some sort of shared memory, save which COM port we have open so we don't try to open it again
// Create threads
readThread = std::thread(&VCP::readTask, this);
writeThread = std::thread(&VCP::writeTask, this);
return true;
}
void VCP::openAsync(fn_boolCallback callback) {
threads.push_back(std::make_shared<std::thread>([&]() {
callback(open(true));
}));
}
bool VCP::close() {
if(!isOpen())
return false;
closing = true; // Signal the threads that we are closing
for(auto& t : threads)
t->join(); // Wait for the threads to close
readThread.join();
writeThread.join();
if(!CloseHandle(handle))
return false;
handle = INVALID_HANDLE_VALUE;
bool ret = true; // If one of the events fails closing, we probably still want to try and close the others
if(overlappedRead.hEvent != INVALID_HANDLE_VALUE) {
if(!CloseHandle(overlappedRead.hEvent))
ret = false;
}
if(overlappedWrite.hEvent != INVALID_HANDLE_VALUE) {
if(!CloseHandle(overlappedWrite.hEvent))
ret = false;
}
if(overlappedWait.hEvent != INVALID_HANDLE_VALUE) {
if(!CloseHandle(overlappedWait.hEvent))
ret = false;
}
// TODO Set up some sort of shared memory, free which COM port we had open so we can try to open it again
return ret;
}
void VCP::readTask() {
constexpr size_t READ_BUFFER_SIZE = 8;
uint8_t readbuf[READ_BUFFER_SIZE];
IOTaskState state = LAUNCH;
DWORD bytesRead = 0;
while(!closing) {
switch(state) {
case LAUNCH: {
COMSTAT comStatus;
unsigned long errorCodes;
if(!ClearCommError(handle, &errorCodes, &comStatus))
std::cout << "Error clearing com err" << std::endl;
bytesRead = 0;
if(ReadFile(handle, readbuf, READ_BUFFER_SIZE, nullptr, &overlappedRead)) {
if(GetOverlappedResult(handle, &overlappedRead, &bytesRead, FALSE)) {
if(bytesRead)
readQueue.enqueue_bulk(readbuf, bytesRead);
} else {
std::cout <<"Readfile succeeded but not enqueued " << GetLastError() << std::endl;
}
continue;
}
auto err = GetLastError();
if(err == ERROR_SUCCESS)
std::cout << "Error was success?" << std::endl;
if(err == ERROR_IO_PENDING)
state = WAIT;
else
std::cout << "ReadFile failed " << err << std::endl;
}
break;
case WAIT: {
auto ret = WaitForSingleObject(overlappedRead.hEvent, 100);
if(ret == WAIT_OBJECT_0) {
auto err = GetLastError();
if(GetOverlappedResult(handle, &overlappedRead, &bytesRead, FALSE)) {
readQueue.enqueue_bulk(readbuf, bytesRead);
state = LAUNCH;
} else
std::cout << "ReadFile deferred failed " << err << std::endl;
}
if(ret == WAIT_ABANDONED) {
state = LAUNCH;
std::cout << "Readfile abandoned" << std::endl;
}
}
}
}
}
void VCP::writeTask() {
IOTaskState state = LAUNCH;
VCP::WriteOperation writeOp;
DWORD bytesWritten = 0;
while(!closing) {
switch(state) {
case LAUNCH: {
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
continue;
bytesWritten = 0;
if(WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &overlappedWrite))
continue;
auto err = GetLastError();
if(err == ERROR_IO_PENDING) {
state = WAIT;
}
else
std::cout << "Writefile failed " << err << std::endl;
}
break;
case WAIT: {
auto ret = WaitForSingleObject(overlappedWrite.hEvent, 50);
if(ret == WAIT_OBJECT_0) {
if(!GetOverlappedResult(handle, &overlappedWrite, &bytesWritten, FALSE)) {
std::cout << "Writefile deferred failed " << GetLastError() << std::endl;
}
state = LAUNCH;
}
if(ret == WAIT_ABANDONED) {
std::cout << "Writefile deferred abandoned" << std::endl;
state = LAUNCH;
}
}
}
}
}