mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-08-05 09:28:40 +02:00
Driver: Switch to libredxx
- no more libFTDI - no more libusb on Linux and macOS - no more FTDI repack - no more binary libs - faster D2XX on Windows (no longer uses COM)
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
#include "icsneo/platform/windows/cdcacm.h"
|
||||
|
||||
#include <setupapi.h>
|
||||
#include <initguid.h>
|
||||
#include <usbiodef.h>
|
||||
#include <devpkey.h>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
CDCACM::CDCACM(const device_eventhandler_t& err, const std::wstring& path) : Driver(err), path(path) {
|
||||
}
|
||||
|
||||
bool CDCACM::open() {
|
||||
handle = CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
|
||||
if(handle == INVALID_HANDLE_VALUE) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
COMMTIMEOUTS timeouts;
|
||||
timeouts.ReadIntervalTimeout = MAXDWORD;
|
||||
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
|
||||
timeouts.ReadTotalTimeoutConstant = MAXDWORD - 1;
|
||||
timeouts.WriteTotalTimeoutMultiplier = 0;
|
||||
timeouts.WriteTotalTimeoutConstant = 0;
|
||||
|
||||
if(!SetCommTimeouts(handle, &timeouts)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
DCB comstate;
|
||||
if(!GetCommState(handle, &comstate)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
comstate.BaudRate = 115200;
|
||||
comstate.ByteSize = 8;
|
||||
comstate.fRtsControl = RTS_CONTROL_DISABLE;
|
||||
if(!SetCommState(handle, &comstate)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
PurgeComm(handle, PURGE_RXCLEAR);
|
||||
|
||||
readOverlapped.hEvent = CreateEventA(nullptr, false, false, nullptr);
|
||||
writeOverlapped.hEvent = CreateEventA(nullptr, false, false, nullptr);
|
||||
|
||||
setIsDisconnected(false);
|
||||
readThread = std::thread(&CDCACM::read, this);
|
||||
writeThread = std::thread(&CDCACM::write, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDCACM::isOpen() {
|
||||
return handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
bool CDCACM::close() {
|
||||
setIsClosing(true);
|
||||
SetEvent(readOverlapped.hEvent); // unblock read thread
|
||||
SetEvent(writeOverlapped.hEvent); // unblock write thread if waiting on COM write
|
||||
writeQueue.enqueue(WriteOperation{}); // unblock write thread if waiting on write queue pop
|
||||
readThread.join();
|
||||
writeThread.join();
|
||||
CloseHandle(readOverlapped.hEvent);
|
||||
CloseHandle(writeOverlapped.hEvent);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
setIsClosing(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDCACM::read() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
std::vector<uint8_t> buffer(ICSNEO_DRIVER_RINGBUFFER_SIZE);
|
||||
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
if(!ReadFile(handle, buffer.data(), (DWORD)buffer.size(), nullptr, &readOverlapped)) {
|
||||
if(GetLastError() != ERROR_IO_PENDING) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
DWORD read = 0;
|
||||
if(!GetOverlappedResult(handle, &readOverlapped, &read, true)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
if(read == 0) {
|
||||
continue;
|
||||
}
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
if(pushRx(buffer.data(), read))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CDCACM::write() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
WriteOperation writeOp;
|
||||
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
writeQueue.wait_dequeue(writeOp);
|
||||
|
||||
if(isDisconnected() || isClosing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &writeOverlapped)) {
|
||||
if(GetLastError() != ERROR_IO_PENDING) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
DWORD written;
|
||||
if(!GetOverlappedResult(handle, &writeOverlapped, &written, true)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(written != writeOp.bytes.size()) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DeviceInfo {
|
||||
public:
|
||||
DeviceInfo() {
|
||||
mDeviceInfo = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
}
|
||||
~DeviceInfo() {
|
||||
SetupDiDestroyDeviceInfoList(mDeviceInfo);
|
||||
}
|
||||
operator HDEVINFO() const {
|
||||
return mDeviceInfo;
|
||||
}
|
||||
operator bool() const {
|
||||
return mDeviceInfo != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
private:
|
||||
HDEVINFO mDeviceInfo;
|
||||
};
|
||||
|
||||
class DeviceInfoData {
|
||||
public:
|
||||
DeviceInfoData() {
|
||||
mDeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
}
|
||||
operator SP_DEVINFO_DATA*() {
|
||||
return &mDeviceInfoData;
|
||||
}
|
||||
private:
|
||||
SP_DEVINFO_DATA mDeviceInfoData;
|
||||
};
|
||||
|
||||
static constexpr size_t WSTRING_ELEMENT_SIZE = sizeof(std::wstring::value_type);
|
||||
|
||||
void CDCACM::Find(std::vector<FoundDevice>& found) {
|
||||
DeviceInfoData deviceInfoData;
|
||||
const std::wstring intrepidUSB(L"USB\\VID_093C");
|
||||
DeviceInfo deviceInfoSet;
|
||||
if(!deviceInfoSet) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
for(DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, deviceInfoData); ++i) {
|
||||
DWORD DataT;
|
||||
DWORD buffersize = 0;
|
||||
|
||||
std::wstring wclass;
|
||||
while(!SetupDiGetDevicePropertyW(deviceInfoSet, deviceInfoData, &DEVPKEY_Device_Class, &DataT, reinterpret_cast<PBYTE>(wclass.data()), static_cast<DWORD>((wclass.size() + 1) * WSTRING_ELEMENT_SIZE), &buffersize, 0)) {
|
||||
wclass.resize((buffersize - 1) / WSTRING_ELEMENT_SIZE);
|
||||
}
|
||||
|
||||
if(wclass != L"Ports") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: is this a bug in Windows? why is this returned size different/wrong? It's like it's not a wstring at all
|
||||
std::wstring deviceInstanceId;
|
||||
while(!SetupDiGetDeviceInstanceIdW(deviceInfoSet, deviceInfoData, deviceInstanceId.data(), static_cast<DWORD>(deviceInstanceId.size() + 1), &buffersize)) {
|
||||
deviceInstanceId.resize(buffersize - 1);
|
||||
}
|
||||
|
||||
if(deviceInstanceId.find(intrepidUSB) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wstring wserial;
|
||||
while(!SetupDiGetDevicePropertyW(deviceInfoSet, deviceInfoData, &DEVPKEY_Device_BusReportedDeviceDesc, &DataT, reinterpret_cast<PBYTE>(wserial.data()), static_cast<DWORD>((wserial.size() + 1) * WSTRING_ELEMENT_SIZE), &buffersize, 0)) {
|
||||
wserial.resize((buffersize - 1) / WSTRING_ELEMENT_SIZE);
|
||||
}
|
||||
|
||||
FoundDevice device;
|
||||
|
||||
if(WideCharToMultiByte(CP_ACP, 0, wserial.c_str(), (int)wserial.size(), device.serial, sizeof(device.serial), NULL, NULL) == 0) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wstring wport;
|
||||
while(!SetupDiGetCustomDevicePropertyW(deviceInfoSet, deviceInfoData, L"PortName", 0, &DataT, reinterpret_cast<PBYTE>(wport.data()), static_cast<DWORD>((wport.size() + 1) * WSTRING_ELEMENT_SIZE), &buffersize)) {
|
||||
wport.resize((buffersize - 1) / WSTRING_ELEMENT_SIZE);
|
||||
}
|
||||
|
||||
const std::wstring path(L"\\\\.\\" + wport);
|
||||
|
||||
device.makeDriver = [path](device_eventhandler_t err, neodevice_t&) {
|
||||
return std::make_unique<CDCACM>(err, path);
|
||||
};
|
||||
|
||||
found.emplace_back(std::move(device));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "icsneo/platform/windows/registry.h"
|
||||
#include "icsneo/platform/windows/strings.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <codecvt>
|
||||
#include <vector>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <string>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
|
||||
#include <icsneo/platform/windows/strings.h>
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
#include "icsneo/platform/windows/ftdi.h"
|
||||
#include "icsneo/platform/windows/strings.h"
|
||||
#include "icsneo/platform/ftdi.h"
|
||||
#include "icsneo/platform/registry.h"
|
||||
#include "icsneo/device/founddevice.h"
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <cwctype>
|
||||
#include <algorithm>
|
||||
#include <codecvt>
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
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;
|
||||
|
||||
struct VCP::Detail {
|
||||
Detail() {
|
||||
overlappedRead.hEvent = INVALID_HANDLE_VALUE;
|
||||
overlappedWrite.hEvent = INVALID_HANDLE_VALUE;
|
||||
overlappedWait.hEvent = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||
OVERLAPPED overlappedRead = {};
|
||||
OVERLAPPED overlappedWrite = {};
|
||||
OVERLAPPED overlappedWait = {};
|
||||
};
|
||||
|
||||
void VCP::Find(std::vector<FoundDevice>& found, std::vector<std::wstring> driverNames) {
|
||||
for(auto& driverName : driverNames) {
|
||||
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))
|
||||
continue;
|
||||
|
||||
for(uint32_t i = 0; i < deviceCount; i++) {
|
||||
FoundDevice device;
|
||||
|
||||
device.makeDriver = [](const device_eventhandler_t& reportFn, neodevice_t& device) {
|
||||
return std::unique_ptr<Driver>(new VCP(reportFn, 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;
|
||||
|
||||
auto pidpos = entry.find(L"PID_");
|
||||
if(pidpos == std::wstring::npos)
|
||||
continue;
|
||||
// We will later use this and startchar to parse the PID
|
||||
|
||||
// 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)
|
||||
oss << entry.substr(startchar + 1, 6); // This is a device with characters in the serial number
|
||||
else
|
||||
oss << sn;
|
||||
|
||||
device.productId = uint16_t(std::wcstol(entry.c_str() + pidpos + 4, nullptr, 16));
|
||||
if(!device.productId)
|
||||
continue;
|
||||
|
||||
std::string serial = convertWideString(oss.str());
|
||||
// The serial number should not have a path slash in it. If it does, that means we don't have the real serial.
|
||||
if(serial.find_first_of('\\') != std::string::npos) {
|
||||
// The serial number was not in the first serenum key where we expected it.
|
||||
// We can try to match the ContainerID with the one in ALL_ENUM\USB and get a serial that way
|
||||
std::wstringstream uess;
|
||||
uess << ALL_ENUM_REG_KEY << L"\\USB\\" << vss.str() << L"&PID_" << std::setfill(L'0') << std::setw(4)
|
||||
<< std::uppercase << std::hex << device.productId << L'\\';
|
||||
std::wstringstream ciss;
|
||||
ciss << ALL_ENUM_REG_KEY << entry;
|
||||
std::wstring containerIDFromEntry, containerIDFromEnum;
|
||||
if(!Registry::Get(ciss.str(), L"ContainerID", containerIDFromEntry))
|
||||
continue; // We did not get a container ID. This can happen on Windows XP and before.
|
||||
if(containerIDFromEntry.empty())
|
||||
continue; // The container ID was empty?
|
||||
std::vector<std::wstring> subkeys;
|
||||
if(!Registry::EnumerateSubkeys(uess.str(), subkeys))
|
||||
continue; // VID/PID combo was not present at all.
|
||||
if(subkeys.empty())
|
||||
continue; // No devices for VID/PID.
|
||||
std::wstring correctSerial;
|
||||
for(auto& subkey : subkeys) {
|
||||
std::wstringstream skss;
|
||||
skss << uess.str() << L'\\' << subkey;
|
||||
if(!Registry::Get(skss.str(), L"ContainerID", containerIDFromEnum))
|
||||
continue;
|
||||
if(containerIDFromEntry != containerIDFromEnum)
|
||||
continue;
|
||||
correctSerial = subkey;
|
||||
break;
|
||||
}
|
||||
if(correctSerial.empty())
|
||||
continue; // Didn't find the device within the subkeys of the enumeration
|
||||
|
||||
sn = 0;
|
||||
conversionError = false;
|
||||
try {
|
||||
sn = std::stoi(correctSerial);
|
||||
}
|
||||
catch(...) {
|
||||
conversionError = true;
|
||||
}
|
||||
|
||||
if(!sn || conversionError) {
|
||||
// This is a device with characters in the serial number
|
||||
if(correctSerial.size() != 6)
|
||||
continue;
|
||||
serial = convertWideString(correctSerial);
|
||||
}
|
||||
else {
|
||||
std::wstringstream soss;
|
||||
soss << sn;
|
||||
serial = convertWideString(soss.str());
|
||||
}
|
||||
|
||||
if(serial.find_first_of('\\') != std::string::npos)
|
||||
continue;
|
||||
}
|
||||
for(char& c : serial)
|
||||
c = static_cast<char>(toupper(c));
|
||||
strcpy_s(device.serial, sizeof(device.serial), serial.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
|
||||
}
|
||||
|
||||
bool alreadyFound = false;
|
||||
FoundDevice* shouldReplace = nullptr;
|
||||
for(auto& foundDev : found) {
|
||||
if((foundDev.handle == device.handle || foundDev.handle == 0 || device.handle == 0) && serial == foundDev.serial) {
|
||||
alreadyFound = true;
|
||||
if(foundDev.handle == 0)
|
||||
shouldReplace = &foundDev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!alreadyFound)
|
||||
found.push_back(device);
|
||||
else if(shouldReplace != nullptr)
|
||||
*shouldReplace = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VCP::VCP(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) {
|
||||
detail = std::make_shared<Detail>();
|
||||
}
|
||||
|
||||
VCP::~VCP() {
|
||||
if(isOpen())
|
||||
close();
|
||||
}
|
||||
|
||||
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)) {
|
||||
report(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!IsHandleValid(device.handle)) {
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
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++) {
|
||||
detail->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()) {
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the timeouts
|
||||
COMMTIMEOUTS timeouts;
|
||||
if(!GetCommTimeouts(detail->handle, &timeouts)) {
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_commtimeouts#remarks
|
||||
timeouts.ReadIntervalTimeout = MAXDWORD;
|
||||
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
|
||||
timeouts.ReadTotalTimeoutConstant = 100;
|
||||
timeouts.WriteTotalTimeoutConstant = 10000;
|
||||
timeouts.WriteTotalTimeoutMultiplier = 0;
|
||||
|
||||
if(!SetCommTimeouts(detail->handle, &timeouts)) {
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the COM state
|
||||
DCB comstate;
|
||||
if(!GetCommState(detail->handle, &comstate)) {
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
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(detail->handle, &comstate)) {
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
PurgeComm(detail->handle, PURGE_RXCLEAR);
|
||||
|
||||
// Set up events so that overlapped IO can work with them
|
||||
detail->overlappedRead.hEvent = CreateEvent(nullptr, false, false, nullptr);
|
||||
detail->overlappedWrite.hEvent = CreateEvent(nullptr, false, false, nullptr);
|
||||
detail->overlappedWait.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||
if (detail->overlappedRead.hEvent == nullptr || detail->overlappedWrite.hEvent == nullptr || detail->overlappedWait.hEvent == nullptr) {
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up event so that we will satisfy overlappedWait when a character comes in
|
||||
if(!SetCommMask(detail->handle, EV_RXCHAR)) {
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
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()) {
|
||||
report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsClosing(true); // Signal the threads that we are closing
|
||||
for(auto& t : threads)
|
||||
t->join(); // Wait for the threads to close
|
||||
readThread.join();
|
||||
writeThread.join();
|
||||
setIsClosing(false);
|
||||
|
||||
if(!CloseHandle(detail->handle)) {
|
||||
report(APIEvent::Type::DriverFailedToClose, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
detail->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(detail->overlappedRead.hEvent != INVALID_HANDLE_VALUE) {
|
||||
if(!CloseHandle(detail->overlappedRead.hEvent))
|
||||
ret = false;
|
||||
detail->overlappedRead.hEvent = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
if(detail->overlappedWrite.hEvent != INVALID_HANDLE_VALUE) {
|
||||
if(!CloseHandle(detail->overlappedWrite.hEvent))
|
||||
ret = false;
|
||||
detail->overlappedWrite.hEvent = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
if(detail->overlappedWait.hEvent != INVALID_HANDLE_VALUE) {
|
||||
if(!CloseHandle(detail->overlappedWait.hEvent))
|
||||
ret = false;
|
||||
detail->overlappedWait.hEvent = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
clearBuffers();
|
||||
|
||||
if(!ret)
|
||||
report(APIEvent::Type::DriverFailedToClose, APIEvent::Severity::Error);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool VCP::isOpen() {
|
||||
return detail->handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
void VCP::readTask() {
|
||||
constexpr size_t READ_BUFFER_SIZE = 10240;
|
||||
uint8_t readbuf[READ_BUFFER_SIZE];
|
||||
IOTaskState state = LAUNCH;
|
||||
DWORD bytesRead = 0;
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
while(!isClosing() && !isDisconnected()) {
|
||||
switch(state) {
|
||||
case LAUNCH: {
|
||||
COMSTAT comStatus;
|
||||
unsigned long errorCodes;
|
||||
ClearCommError(detail->handle, &errorCodes, &comStatus);
|
||||
|
||||
bytesRead = 0;
|
||||
if(ReadFile(detail->handle, readbuf, READ_BUFFER_SIZE, nullptr, &detail->overlappedRead)) {
|
||||
if(GetOverlappedResult(detail->handle, &detail->overlappedRead, &bytesRead, FALSE)) {
|
||||
if(bytesRead)
|
||||
pushRx(readbuf, bytesRead);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto lastError = GetLastError();
|
||||
if(lastError == ERROR_IO_PENDING)
|
||||
state = WAIT;
|
||||
else if(lastError != ERROR_SUCCESS) {
|
||||
if(lastError == ERROR_ACCESS_DENIED) {
|
||||
if(!isDisconnected()) {
|
||||
setIsDisconnected(true);
|
||||
report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error);
|
||||
}
|
||||
} else
|
||||
report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WAIT: {
|
||||
auto ret = WaitForSingleObject(detail->overlappedRead.hEvent, 100);
|
||||
if(ret == WAIT_OBJECT_0) {
|
||||
if(GetOverlappedResult(detail->handle, &detail->overlappedRead, &bytesRead, FALSE)) {
|
||||
pushRx(readbuf, bytesRead);
|
||||
state = LAUNCH;
|
||||
} else
|
||||
report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error);
|
||||
}
|
||||
if(ret == WAIT_ABANDONED || ret == WAIT_FAILED) {
|
||||
state = LAUNCH;
|
||||
report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VCP::writeTask() {
|
||||
IOTaskState state = LAUNCH;
|
||||
VCP::WriteOperation writeOp;
|
||||
DWORD bytesWritten = 0;
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
while(!isClosing() && !isDisconnected()) {
|
||||
switch(state) {
|
||||
case LAUNCH: {
|
||||
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
|
||||
continue;
|
||||
|
||||
bytesWritten = 0;
|
||||
if(WriteFile(detail->handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &detail->overlappedWrite))
|
||||
continue;
|
||||
|
||||
auto winerr = GetLastError();
|
||||
if(winerr == ERROR_IO_PENDING) {
|
||||
state = WAIT;
|
||||
}
|
||||
else if(winerr == ERROR_ACCESS_DENIED) {
|
||||
if(!isDisconnected()) {
|
||||
setIsDisconnected(true);
|
||||
report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error);
|
||||
}
|
||||
} else
|
||||
report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
|
||||
}
|
||||
break;
|
||||
case WAIT: {
|
||||
auto ret = WaitForSingleObject(detail->overlappedWrite.hEvent, 50);
|
||||
if(ret == WAIT_OBJECT_0) {
|
||||
if(!GetOverlappedResult(detail->handle, &detail->overlappedWrite, &bytesWritten, FALSE))
|
||||
report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
|
||||
state = LAUNCH;
|
||||
}
|
||||
|
||||
if(ret == WAIT_ABANDONED) {
|
||||
report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
|
||||
state = LAUNCH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user