From 0cf1e7fe7f0f148588a51e3aa578cfbd842d0e24 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Tue, 13 Nov 2018 16:18:57 -0500 Subject: [PATCH 1/8] Resolve compilation errors and warnings with MSVC --- api/icsneocpp/error.cpp | 10 ++++---- include/icsneo/api/error.h | 7 ++++-- include/icsneo/api/errormanager.h | 6 ++--- include/icsneo/device/device.h | 12 +++++----- .../icsneo/device/neovifire2/neovifire2eth.h | 24 +++++++++---------- include/icsneo/device/radgalaxy/radgalaxy.h | 13 +++++----- include/icsneo/device/radstar2/radstar2eth.h | 1 - include/icsneo/platform/windows/pcap.h | 1 + platform/windows/vcp.cpp | 5 ++-- 9 files changed, 39 insertions(+), 40 deletions(-) diff --git a/api/icsneocpp/error.cpp b/api/icsneocpp/error.cpp index cb870f2..87bd4bb 100644 --- a/api/icsneocpp/error.cpp +++ b/api/icsneocpp/error.cpp @@ -17,11 +17,11 @@ APIError::APIError(ErrorType error, const Device* forDevice) : errorStruct({}) { } void APIError::init(ErrorType error) { - timepoint = std::chrono::high_resolution_clock::now(); + timepoint = ErrorClock::now(); errorStruct.description = DescriptionForType(error); errorStruct.errorNumber = (uint32_t)error; errorStruct.severity = (uint8_t)SeverityForType(error); - errorStruct.timestamp = std::chrono::high_resolution_clock::to_time_t(timepoint); + errorStruct.timestamp = ErrorClock::to_time_t(timepoint); } std::string APIError::describe() const noexcept { @@ -35,10 +35,10 @@ std::string APIError::describe() const noexcept { return ss.str(); } -bool APIError::isForDevice(std::string serial) const noexcept { - if(!device || serial.length() == 0) +bool APIError::isForDevice(std::string filterSerial) const noexcept { + if(!device || filterSerial.length() == 0) return false; - return device->getSerial() == serial; + return device->getSerial() == filterSerial; } // API Errors diff --git a/include/icsneo/api/error.h b/include/icsneo/api/error.h index 1620e9e..5d5dd3e 100644 --- a/include/icsneo/api/error.h +++ b/include/icsneo/api/error.h @@ -26,6 +26,9 @@ class Device; class APIError { public: + typedef std::chrono::system_clock ErrorClock; + typedef std::chrono::time_point ErrorTimePoint; + enum ErrorType : uint32_t { Any = 0, // Used for filtering, should not appear in data @@ -73,7 +76,7 @@ public: Severity getSeverity() const noexcept { return Severity(errorStruct.severity); } std::string getDescription() const noexcept { return std::string(errorStruct.description); } const Device* getDevice() const noexcept { return device; } // Will return nullptr if this is an API-wide error - std::chrono::time_point getTimestamp() const noexcept { return timepoint; } + ErrorTimePoint getTimestamp() const noexcept { return timepoint; } bool isForDevice(const Device* forDevice) const noexcept { return forDevice == device; } bool isForDevice(std::string serial) const noexcept; @@ -91,7 +94,7 @@ public: private: neoerror_t errorStruct; std::string serial; - std::chrono::time_point timepoint; + ErrorTimePoint timepoint; const Device* device; void init(ErrorType error); diff --git a/include/icsneo/api/errormanager.h b/include/icsneo/api/errormanager.h index eb4b43e..525387c 100644 --- a/include/icsneo/api/errormanager.h +++ b/include/icsneo/api/errormanager.h @@ -26,9 +26,9 @@ public: get(ret, filter, max); return ret; } - void get(std::vector& errors, ErrorFilter filter, size_t max = 0) { get(errors, max, filter); } - void get(std::vector& errors, size_t max = 0, ErrorFilter filter = ErrorFilter()); - bool getLastError(APIError& error, ErrorFilter filter = ErrorFilter()); + void get(std::vector& outErrors, ErrorFilter filter, size_t max = 0) { get(outErrors, max, filter); } + void get(std::vector& outErrors, size_t max = 0, ErrorFilter filter = ErrorFilter()); + bool getLastError(APIError& outErrors, ErrorFilter filter = ErrorFilter()); void add(APIError error) { std::lock_guard lk(mutex); diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index 466a3f1..5681748 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -103,29 +103,29 @@ protected: template std::unique_ptr makeTransport() { return std::unique_ptr(new Transport(err, getWritableNeoDevice())); } - virtual void setupTransport(ICommunication* transport) {} + virtual void setupTransport(ICommunication* stransport) { (void)stransport; } virtual std::shared_ptr makePacketizer() { return std::make_shared(err); } - virtual void setupPacketizer(Packetizer* packetizer) {} + virtual void setupPacketizer(Packetizer* spacketizer) { (void)spacketizer; } virtual std::unique_ptr makeEncoder(std::shared_ptr p) { return std::unique_ptr(new Encoder(err, p)); } - virtual void setupEncoder(Encoder* encoder) {} + virtual void setupEncoder(Encoder* sencoder) { (void)sencoder; } virtual std::unique_ptr makeDecoder() { return std::unique_ptr(new Decoder(err)); } - virtual void setupDecoder(Decoder* decoder) {} + virtual void setupDecoder(Decoder* sdecoder) { (void)sdecoder; } virtual std::shared_ptr makeCommunication( std::unique_ptr t, std::shared_ptr p, std::unique_ptr e, std::unique_ptr d) { return std::make_shared(err, std::move(t), p, std::move(e), std::move(d)); } - virtual void setupCommunication(Communication* com) {} + virtual void setupCommunication(Communication* scom) { (void)scom; } template std::unique_ptr makeSettings(std::shared_ptr com) { return std::unique_ptr(new Settings(com)); } - virtual void setupSettings(IDeviceSettings* settings) {} + virtual void setupSettings(IDeviceSettings* ssettings) { (void)ssettings; } // END Initialization Functions void handleInternalMessage(std::shared_ptr message); diff --git a/include/icsneo/device/neovifire2/neovifire2eth.h b/include/icsneo/device/neovifire2/neovifire2eth.h index 3cabb84..13fcb89 100644 --- a/include/icsneo/device/neovifire2/neovifire2eth.h +++ b/include/icsneo/device/neovifire2/neovifire2eth.h @@ -15,13 +15,12 @@ public: std::vector> found; for(auto& foundDev : PCAP::FindAll()) { - auto packetizer = std::make_shared(); - auto decoder = std::unique_ptr(new Decoder()); - for(auto& payload : foundDev.discoveryPackets) - packetizer->input(payload); - for(auto& packet : packetizer->output()) { + auto fakedev = std::shared_ptr(new NeoVIFIRE2ETH({})); + for (auto& payload : foundDev.discoveryPackets) + fakedev->com->packetizer->input(payload); + for (auto& packet : fakedev->com->packetizer->output()) { std::shared_ptr msg; - if(!decoder->decode(msg, packet)) + if (!fakedev->com->decoder->decode(msg, packet)) continue; // We failed to decode this packet if(!msg || msg->network.getNetID() != Network::NetID::Main51) @@ -44,17 +43,16 @@ public: return found; } -protected: - virtual void setupSettings(IDeviceSettings* settings) { - // TODO Check firmware version, old firmwares will reset Ethernet settings on settings send - settings->readonly = true; - } - -private: NeoVIFIRE2ETH(neodevice_t neodevice) : NeoVIFIRE2(neodevice) { initialize(); productId = PRODUCT_ID; } + +protected: + virtual void setupSettings(IDeviceSettings* ssettings) { + // TODO Check firmware version, old firmwares will reset Ethernet settings on settings send + ssettings->readonly = true; + } }; } diff --git a/include/icsneo/device/radgalaxy/radgalaxy.h b/include/icsneo/device/radgalaxy/radgalaxy.h index f8b5b97..b6f7719 100644 --- a/include/icsneo/device/radgalaxy/radgalaxy.h +++ b/include/icsneo/device/radgalaxy/radgalaxy.h @@ -47,18 +47,17 @@ public: return found; } -protected: - void setupPacketizer(Packetizer* packetizer) override { - packetizer->disableChecksum = true; - packetizer->align16bit = false; - } - -private: RADGalaxy(neodevice_t neodevice) : Device(neodevice) { initialize(); getWritableNeoDevice().type = DEVICE_TYPE; productId = PRODUCT_ID; } + +protected: + void setupPacketizer(Packetizer* packetizer) override { + packetizer->disableChecksum = true; + packetizer->align16bit = false; + } }; } diff --git a/include/icsneo/device/radstar2/radstar2eth.h b/include/icsneo/device/radstar2/radstar2eth.h index 62a17c6..8821aed 100644 --- a/include/icsneo/device/radstar2/radstar2eth.h +++ b/include/icsneo/device/radstar2/radstar2eth.h @@ -43,7 +43,6 @@ public: return found; } -private: RADStar2ETH(neodevice_t neodevice) : RADStar2(neodevice) { initialize(); } diff --git a/include/icsneo/platform/windows/pcap.h b/include/icsneo/platform/windows/pcap.h index 1d9bca8..137b5ba 100644 --- a/include/icsneo/platform/windows/pcap.h +++ b/include/icsneo/platform/windows/pcap.h @@ -27,6 +27,7 @@ public: bool close(); private: PCAPDLL pcap; + device_errorhandler_t err; char errbuf[PCAP_ERRBUF_SIZE] = { 0 }; neodevice_t& device; uint8_t deviceMAC[6]; diff --git a/platform/windows/vcp.cpp b/platform/windows/vcp.cpp index 6ebc279..7f6f416 100644 --- a/platform/windows/vcp.cpp +++ b/platform/windows/vcp.cpp @@ -270,7 +270,6 @@ void VCP::readTask() { 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; @@ -300,8 +299,8 @@ void VCP::writeTask() { if(WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &overlappedWrite)) continue; - auto err = GetLastError(); - if(err == ERROR_IO_PENDING) { + auto winerr = GetLastError(); + if(winerr == ERROR_IO_PENDING) { state = WAIT; } else From c9f761c262ea390d7fed24d43ecf141b070cc3a5 Mon Sep 17 00:00:00 2001 From: Jeffrey Quesnelle Date: Wed, 14 Nov 2018 11:24:55 -0500 Subject: [PATCH 2/8] add Ethernet cases to Network --- include/icsneo/communication/network.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/icsneo/communication/network.h b/include/icsneo/communication/network.h index f0bb294..5c0fb60 100644 --- a/include/icsneo/communication/network.h +++ b/include/icsneo/communication/network.h @@ -142,6 +142,8 @@ public: return "Other"; case Type::Internal: return "Internal"; + case Type::Ethernet: + return "Ethernet"; case Type::Invalid: default: return "Invalid Type"; @@ -187,6 +189,21 @@ public: case NetID::Invalid: case NetID::Any: return Type::Invalid; + case NetID::Ethernet: + case NetID::Ethernet_DAQ: + case NetID::OP_Ethernet1: + case NetID::OP_Ethernet2: + case NetID::OP_Ethernet3: + case NetID::OP_Ethernet4: + case NetID::OP_Ethernet5: + case NetID::OP_Ethernet6: + case NetID::OP_Ethernet7: + case NetID::OP_Ethernet8: + case NetID::OP_Ethernet9: + case NetID::OP_Ethernet10: + case NetID::OP_Ethernet11: + case NetID::OP_Ethernet12: + return Type::Ethernet; default: return Type::Other; } From 07a5dc41188b8e697d215a74bede5782320e4e87 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 16 Nov 2018 17:08:53 -0500 Subject: [PATCH 3/8] Allow threads to reopen after closing --- communication/communication.cpp | 3 ++- communication/multichannelcommunication.cpp | 2 ++ device/device.cpp | 1 + platform/posix/ftdi.cpp | 2 ++ platform/posix/stm32.cpp | 2 ++ platform/windows/pcap.cpp | 1 + platform/windows/vcp.cpp | 4 ++++ 7 files changed, 14 insertions(+), 1 deletion(-) diff --git a/communication/communication.cpp b/communication/communication.cpp index 5b413e6..320a03e 100644 --- a/communication/communication.cpp +++ b/communication/communication.cpp @@ -31,8 +31,10 @@ void Communication::spawnThreads() { } void Communication::joinThreads() { + closing = true; if(readTaskThread.joinable()) readTaskThread.join(); + closing = false; } bool Communication::close() { @@ -40,7 +42,6 @@ bool Communication::close() { return false; isOpen = false; - closing = true; joinThreads(); return impl->close(); diff --git a/communication/multichannelcommunication.cpp b/communication/multichannelcommunication.cpp index 276880d..aa4c920 100644 --- a/communication/multichannelcommunication.cpp +++ b/communication/multichannelcommunication.cpp @@ -12,8 +12,10 @@ void MultiChannelCommunication::spawnThreads() { } void MultiChannelCommunication::joinThreads() { + closing = true; if(mainChannelReadThread.joinable()) mainChannelReadThread.join(); + closing = false; } bool MultiChannelCommunication::sendPacket(std::vector& bytes) { diff --git a/device/device.cpp b/device/device.cpp index 1b62fcb..1d6b043 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -180,6 +180,7 @@ bool Device::close() { if(internalHandlerCallbackID) com->removeMessageCallback(internalHandlerCallbackID); + goOffline(); return com->close(); } diff --git a/platform/posix/ftdi.cpp b/platform/posix/ftdi.cpp index 9c9fb78..741d6fc 100644 --- a/platform/posix/ftdi.cpp +++ b/platform/posix/ftdi.cpp @@ -97,6 +97,8 @@ bool FTDI::close() { if(writeThread.joinable()) writeThread.join(); + closing = false; + if(ftdiDevice.close()) return false; diff --git a/platform/posix/stm32.cpp b/platform/posix/stm32.cpp index 7fca33c..99ce634 100644 --- a/platform/posix/stm32.cpp +++ b/platform/posix/stm32.cpp @@ -252,6 +252,8 @@ bool STM32::close() { if(writeThread.joinable()) writeThread.join(); + closing = false; + int ret = ::close(fd); fd = -1; diff --git a/platform/windows/pcap.cpp b/platform/windows/pcap.cpp index 803af8a..7b3e469 100644 --- a/platform/windows/pcap.cpp +++ b/platform/windows/pcap.cpp @@ -228,6 +228,7 @@ bool PCAP::close() { closing = true; // Signal the threads that we are closing readThread.join(); writeThread.join(); + closing = false; pcap.close(interface.fp); interface.fp = nullptr; diff --git a/platform/windows/vcp.cpp b/platform/windows/vcp.cpp index 7f6f416..e22930f 100644 --- a/platform/windows/vcp.cpp +++ b/platform/windows/vcp.cpp @@ -214,6 +214,7 @@ bool VCP::close() { t->join(); // Wait for the threads to close readThread.join(); writeThread.join(); + closing = false; if(!CloseHandle(handle)) return false; @@ -224,14 +225,17 @@ bool VCP::close() { if(overlappedRead.hEvent != INVALID_HANDLE_VALUE) { if(!CloseHandle(overlappedRead.hEvent)) ret = false; + overlappedRead.hEvent = INVALID_HANDLE_VALUE; } if(overlappedWrite.hEvent != INVALID_HANDLE_VALUE) { if(!CloseHandle(overlappedWrite.hEvent)) ret = false; + overlappedWrite.hEvent = INVALID_HANDLE_VALUE; } if(overlappedWait.hEvent != INVALID_HANDLE_VALUE) { if(!CloseHandle(overlappedWait.hEvent)) ret = false; + overlappedWait.hEvent = INVALID_HANDLE_VALUE; } // TODO Set up some sort of shared memory, free which COM port we had open so we can try to open it again From 82adbcaba66f356262d072207734197aa222e5ae Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 16 Nov 2018 18:48:07 -0500 Subject: [PATCH 4/8] Windows VCP detection is now more reliable --- include/icsneo/platform/windows/ftdi.h | 2 +- include/icsneo/platform/windows/stm32.h | 2 +- include/icsneo/platform/windows/vcp.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/icsneo/platform/windows/ftdi.h b/include/icsneo/platform/windows/ftdi.h index 860a845..d693248 100644 --- a/include/icsneo/platform/windows/ftdi.h +++ b/include/icsneo/platform/windows/ftdi.h @@ -8,7 +8,7 @@ namespace icsneo { class FTDI : public VCP { public: FTDI(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {} - static std::vector FindByProduct(int product) { return VCP::FindByProduct(product, L"serenum"); } + static std::vector FindByProduct(int product) { return VCP::FindByProduct(product, { L"serenum", L"ftdibus" }); } }; } diff --git a/include/icsneo/platform/windows/stm32.h b/include/icsneo/platform/windows/stm32.h index 0b0dc18..6b7acba 100644 --- a/include/icsneo/platform/windows/stm32.h +++ b/include/icsneo/platform/windows/stm32.h @@ -8,7 +8,7 @@ namespace icsneo { class STM32 : public VCP { public: STM32(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {} - static std::vector FindByProduct(int product) { return VCP::FindByProduct(product, L"usbser"); } + static std::vector FindByProduct(int product) { return VCP::FindByProduct(product, { L"usbser" }); } }; } diff --git a/include/icsneo/platform/windows/vcp.h b/include/icsneo/platform/windows/vcp.h index dd8baa8..25a3b2b 100644 --- a/include/icsneo/platform/windows/vcp.h +++ b/include/icsneo/platform/windows/vcp.h @@ -16,7 +16,7 @@ namespace icsneo { // Virtual COM Port Communication class VCP : public ICommunication { public: - static std::vector FindByProduct(int product, wchar_t* driverName); + static std::vector FindByProduct(int product, std::vector driverName); static bool IsHandleValid(neodevice_handle_t handle); typedef void(*fn_boolCallback)(bool success); From 8a147e2c3fd083de09f650f20a60484344715b29 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 16 Nov 2018 18:48:28 -0500 Subject: [PATCH 5/8] Purge read and write queues after a close succeeds --- platform/posix/ftdi.cpp | 10 ++- platform/posix/stm32.cpp | 5 ++ platform/windows/pcap.cpp | 5 ++ platform/windows/vcp.cpp | 169 ++++++++++++++++++++++---------------- 4 files changed, 118 insertions(+), 71 deletions(-) diff --git a/platform/posix/ftdi.cpp b/platform/posix/ftdi.cpp index 741d6fc..fe83638 100644 --- a/platform/posix/ftdi.cpp +++ b/platform/posix/ftdi.cpp @@ -99,10 +99,16 @@ bool FTDI::close() { closing = false; + bool ret = true; if(ftdiDevice.close()) - return false; + ret = false; - return true; + uint8_t flush; + WriteOperation flushop; + while(readQueue.try_dequeue(flush)) {} + while(writeQueue.try_dequeue(flushop)) {} + + return ret; } void FTDI::readTask() { diff --git a/platform/posix/stm32.cpp b/platform/posix/stm32.cpp index 99ce634..2365848 100644 --- a/platform/posix/stm32.cpp +++ b/platform/posix/stm32.cpp @@ -256,6 +256,11 @@ bool STM32::close() { int ret = ::close(fd); fd = -1; + + uint8_t flush; + WriteOperation flushop; + while (readQueue.try_dequeue(flush)) {} + while (writeQueue.try_dequeue(flushop)) {} return ret == 0; } diff --git a/platform/windows/pcap.cpp b/platform/windows/pcap.cpp index 7b3e469..469b219 100644 --- a/platform/windows/pcap.cpp +++ b/platform/windows/pcap.cpp @@ -233,6 +233,11 @@ bool PCAP::close() { pcap.close(interface.fp); interface.fp = nullptr; + uint8_t flush; + WriteOperation flushop; + while(readQueue.try_dequeue(flush)) {} + while(writeQueue.try_dequeue(flushop)) {} + return true; } diff --git a/platform/windows/vcp.cpp b/platform/windows/vcp.cpp index e22930f..30d463c 100644 --- a/platform/windows/vcp.cpp +++ b/platform/windows/vcp.cpp @@ -18,82 +18,99 @@ 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 VCP::FindByProduct(int product, wchar_t* driverName) { +std::vector VCP::FindByProduct(int product, std::vector driverNames) { std::vector found; - std::wstringstream regss; - regss << DRIVER_SERVICES_REG_KEY << driverName << L"\\Enum\\"; - std::wstring driverEnumRegKey = regss.str(); + 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)) { - 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; + uint32_t deviceCount = 0; + if(!Registry::Get(driverEnumRegKey, L"Count", deviceCount)) { + return found; } - 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; - } + for(uint32_t i = 0; i < deviceCount; i++) { + neodevice_t device = {}; - strcpy_s(device.serial, sizeof(device.serial), converter.to_bytes(oss.str()).c_str()); + // 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; - // 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) { + 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 { - device.handle = std::stoi(port.substr(compos + 3)); - } catch(...) {} // In case of this, or any other error, handle has already been initialized to 0 - } + sn = std::stoi(entry.substr(startchar + 1)); + } + catch(...) { + conversionError = true; + } - found.push_back(device); + 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; + } + + std::string serial = converter.to_bytes(oss.str()); + if(serial.find_first_of('\\') != std::string::npos) + continue; // Not sure how this happened but a slash is not valid in the serial + 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; + for(auto& foundDev : found) { + if(foundDev.handle == device.handle && serial == foundDev.serial) { + alreadyFound = true; + break; + } + } + + if(!alreadyFound) + found.push_back(device); + } } return found; @@ -113,8 +130,10 @@ bool VCP::open(bool fromAsync) { if(isOpen() || (!fromAsync && opening)) return false; - if(!IsHandleValid(device.handle)) + if(!IsHandleValid(device.handle)) { + err(APIError::DriverFailedToOpen); return false; + } opening = true; @@ -132,8 +151,10 @@ bool VCP::open(bool fromAsync) { opening = false; - if(!isOpen()) + if(!isOpen()) { + err(APIError::DriverFailedToOpen); return false; + } // Set the timeouts COMMTIMEOUTS timeouts; @@ -151,6 +172,7 @@ bool VCP::open(bool fromAsync) { if(!SetCommTimeouts(handle, &timeouts)) { close(); + err(APIError::DriverFailedToOpen); return false; } @@ -158,6 +180,7 @@ bool VCP::open(bool fromAsync) { DCB comstate; if(!GetCommState(handle, &comstate)) { close(); + err(APIError::DriverFailedToOpen); return false; } @@ -170,6 +193,7 @@ bool VCP::open(bool fromAsync) { if(!SetCommState(handle, &comstate)) { close(); + err(APIError::DriverFailedToOpen); return false; } @@ -181,12 +205,14 @@ bool VCP::open(bool fromAsync) { overlappedWait.hEvent = CreateEvent(nullptr, true, false, nullptr); if (overlappedRead.hEvent == nullptr || overlappedWrite.hEvent == nullptr || overlappedWait.hEvent == nullptr) { close(); + err(APIError::DriverFailedToOpen); return false; } // Set up event so that we will satisfy overlappedWait when a character comes in if(!SetCommMask(handle, EV_RXCHAR)) { close(); + err(APIError::DriverFailedToOpen); return false; } @@ -238,6 +264,11 @@ bool VCP::close() { overlappedWait.hEvent = INVALID_HANDLE_VALUE; } + uint8_t flush; + WriteOperation flushop; + while(readQueue.try_dequeue(flush)) {} + while(writeQueue.try_dequeue(flushop)) {} + // 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; From 7305f2027ef6f1320c33ee0dac20aef39081f57e Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Mon, 19 Nov 2018 12:20:45 -0500 Subject: [PATCH 6/8] Larger message structure for future expansion --- include/icsneo/communication/message/neomessage.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/icsneo/communication/message/neomessage.h b/include/icsneo/communication/message/neomessage.h index 53f5798..79f2a28 100644 --- a/include/icsneo/communication/message/neomessage.h +++ b/include/icsneo/communication/message/neomessage.h @@ -107,8 +107,8 @@ typedef struct { uint8_t header[4]; uint16_t netid; uint8_t type; - uint8_t reserved[9]; -} neomessage_t; + uint8_t reserved[17]; +} neomessage_t; // 64 bytes total // Any time you add another neomessage_*_t type, make sure to add it to the static_asserts below! typedef struct { @@ -120,7 +120,7 @@ typedef struct { uint16_t netid; uint8_t type; uint8_t dlcOnWire; - char reserved[8]; + char reserved[16]; } neomessage_can_t; #pragma pack(pop) From 5c3eb3f6e6036536aa9809346f4d10494fbdceaa Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Mon, 19 Nov 2018 12:21:06 -0500 Subject: [PATCH 7/8] Update hardware support document --- .gitignore | 3 ++- HARDWARE.md | 50 +++++++++++++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index fc13cc1..d88dec8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ build*/ Thumbs.db .vscode/settings.json third-party/concurrentqueue/benchmarks -third-party/concurrentqueue/tests \ No newline at end of file +third-party/concurrentqueue/tests +*.bak diff --git a/HARDWARE.md b/HARDWARE.md index f8cd6db..8b50eea 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -1,26 +1,30 @@ -Hardware -========= +# Hardware Support -Ethernet devices - neoVI FIRE 2 - CAN 2.0 works - RADGalaxy - CAN 2.0 works +- Ethernet devices + - neoVI FIRE 2 + - CAN works + - CAN FD works + - RADGalaxy + - CAN works -STM32 devices - ValueCAN 4 - CAN 2.0 works - neoOBD2 PRO - CAN 2.0 works - neoOBD2-SIM - Connects +- STM32 devices + - ValueCAN 4 + - CAN works + - CAN FD works + - neoOBD2 PRO + - CAN works -FTDI devices - neoVI FIRE - HSCAN works - neoVI FIRE 2 - CAN 2.0 works - ValueCAN 3 - CAN works - RADStar 2 - CAN works \ No newline at end of file +- FTDI devices + - neoVI FIRE + - CAN works + - neoVI FIRE 2 + - CAN works + - CAN FD works + - ValueCAN 3 + - CAN works + - RADStar 2 + - CAN works + - neoVI PLASMA + - CAN works + - neoVI ION + - CAN works \ No newline at end of file From 9f7f2c5f556a29f3cf044e65c3f359f8ac1137a5 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Mon, 19 Nov 2018 20:23:36 -0500 Subject: [PATCH 8/8] Include README and LICENSE --- LICENSE | 13 ++++++ README.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..48baaaf --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2018 Intrepid Control Systems, Inc. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +4. It is forbidden to use this library or derivatives to interface with vehicle networking hardware not produced by Intrepid Control Systems, Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..46d121b --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# libicsneo +--- +### Intrepid Control Systems Open Cross-Platform Device Communication API + +An open source solution for communicating with Intrepid Control Systems devices. + +## Getting Started +--- +There are two major ways to write a new application using libicsneo. You can use the C++ interface, which will be compiled with your project and statically linked, or you can use the C interface, which can be either statically or dynamically linked. +### Integration with CMake (Static Linking) +Integrating the library with your current CMake project is extremely easy. +1. Checkout the library (or add as a submodule) into a subdirectory of your project. +2. Within your `CMakeLists.txt` you can add the line `add_subdirectory("third-party/libicsneo")` to bring in the libicsneo targets. Replace `third-party` with any subdirectory you choose. +3. The libicsneo library include paths should automatically be added to your include path. +4. Link the library with your target by adding `target_link_libraries(libicsneocpp-example icsneocpp)` after your target, substituting `libicsneocpp-example` with your target application. + +You can now include either the C++ API with `#include ` or the C API with `#include ` + +### DLL / SO / DYLIB Releases (Dynamic Linking) +It is also possible to use the precompiled binaries with runtime linking. It is not recommended or supported to attempt to use the C++ interface with dynamic linking due to the complexities of C++ compilers. +1. Add this repository's `/include` to your include path +2. Add `#define ICSNEOC_DYNAMICLOAD` to the top of your source file +2. Add `#import ` below that line +3. Call `icsneo_init();` to import the library before using any other libicsneo functions. +4. Use the library as normal. +5. Call `icsneo_close();` to unload the library. + +## Usage +--- +### Using the C++ API +The C++ API is designed to be modern and easy to use. All library functions and classes are in the namespace `icsneo`. Most applications will start by calling `icsneo::FindAllDevices()`. This will return an `std::vector` of `std::shared_ptr` objects. You will want to keep a copy of the `shared_ptr` to any devices you want to use, as allowing it to go out of scope will automatically close the device and free all memory associated with it. + +Any time you get bus traffic from the API, you will receive it as an `std::shared_ptr`. The message will be valid as long as the `shared_ptr` stays in scope. Checking the type of the message allows you to cast it accordingly and access extra data for certain protocols. For instance, casting an `icsneo::Message` to an `icsneo::CANMessage` allows you to access the arbitration ID. + +A barebones example is provided. For a more complete example, check intrepidcs/libicsneocpp-example. +``` c++ +std::vector> devices = icsneo::FindAllDevices(); +std::cout << devices.size() << " found!" << std::endl; +for(auto& device : devices) + std::cout << "Found " << device->describe() << std::endl; // "Found neoVI FIRE 2 CY2345" +std::shared_ptr myDevice = devices[0]; +if(!myDevice->open()) { + // There was an error while attempting to open the device, print the error details + for(auto& error : icsneo::getErrors()) + std::cout << error << std::endl; +} +myDevice->goOnline(); // Start receiving messages +myDevice->enableMessagePolling(); // Allow the use of myDevice->getMessages() later +// Alternatively, assign a callback for new messages +std::this_thread::wait_for(std::chrono::seconds(5)); +std::vector> messages = myDevice->getMessages(); +std::cout << "We got " << messages.size() << " messages!" << std::endl; +for(auto& msg : messages) { + if(msg->network.getType() == icsneo::Network::Type::CAN) { + // A message of type CAN is guaranteed to be a CANMessage, so we can static cast safely + auto canmsg = std::static_pointer_cast(msg); + // canmsg->arbid is valid here + // canmsg->data is an std::vector, you can check .size() for the DLC of the message + // canmsg->timestamp is the time recorded by the hardware in nanoseconds since (1/1/2007 12:00:00 GMT) + } +} +myDevice->close(); +``` + +### Using the C API +The C API is designed to be a robust and fault tolerant interface which allows easy integration with other languages as well as existing C applications. When calling `icsneo_findAllDevices()` you will provide a buffer of `neodevice_t` structures, which will be written with the found devices. These `neodevice_t` structures can be uses to interface with the API from then on. Once you call `icsneo_close()` with a device, that device and all associated memory will be freed. You will need to run `icsneo_findAllDevices()` again to reconnect. + +Messages are passed in the form of `neomessage_t` structures when calling `icsneo_getMessages()`. These structures contain a `uint8_t*` to the payload data, and this pointer will be valid until the next call to `icsneo_getMessages()` or the device is closed. + +A barebones example is provided. For a more complete example, check intrepidcs/libicsneoc-example. +``` c +size_t deviceCount = 10; // Pre-set to the size of your buffer before the icsneo_findAllDevices() call +neodevice_t devices[10]; +icsneo_findAllDevices(devices, &deviceCount); +printf("We found %ull devices\n", deviceCount); +for(size_t i = 0; i < deviceCount; i++) { + neodevice_t* myDevice = &devices[i]; + char desc[ICSNEO_DEVICETYPE_LONGEST_DESCRIPTION]; + size_t sz = ICSNEO_DEVICETYPE_LONGEST_DESCRIPTION; + icsneo_describeDevice(myDevice, desc, &sz); + printf("Found %s\n", desc); // "Found neoVI FIRE 2 CY2345" +} + +neodevice_t* myDevice = &devices[0]; +if(!icsneo_openDevice(myDevice)) { + neoerror_t error; + if(icsneo_getLastError(&error)) + printf("Error! %s\n", error.description); +} +icsneo_goOnline(myDevice); // Start receiving messages +icsneo_enableMessagePolling(myDevice); // Allow the use of icsneo_getMessages() later +sleep(5); +neomessage_t messages[50]; +size_t messageCount = 50; +icsneo_getMessages(myDevice, messages, &messageCount, 0 /* non-blocking */); +printf("We got %ull messages!\n", messageCount); +for(size_t i = 0; i < messageCount; i++) { + if(messages[i].type == ICSNEO_NETWORK_TYPE_CAN) { + // A message of type CAN should be interperated a neomessage_can_t, so we can cast safely + neomessage_can_t* canmsg = (neomessage_can_t*)&messages[i]; + // canmsg->arbid is valid here + // canmsg->data is an uint8_t*, you can check canmsg->length for the length of the payload + // canmsg->timestamp is the time recorded by the hardware in nanoseconds since (1/1/2007 12:00:00 GMT) + } +} +icsneo_closeDevice(myDevice); +``` + +## Building from Source +--- +### Windows +Building will require Microsoft Visual Studio 2017 and CMake to be installed. +### macOS +Getting the dependencies is easiest with the Homebrew package manager. You will also need XCode installed. You can then install CMake, an up-to-date version of GCC or Clang, and `libusb-1.0`. +### Linux +The dependencies are as follows + - CMake 3.2 or above + - GCC 4.7 or above, 4.8+ recommended + - `libusb-1.0-dev` + - `libboost-dev` + - `build-essential` on Ubuntu is recommended \ No newline at end of file