mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-08-05 09:28:40 +02:00
Initial commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user