Merge branch 'master' of intrepidcs.homeip.net:paulhollinsky/icsneonext

pull/4/head
Paul Hollinsky 2018-11-20 09:42:29 -05:00
commit 2ade9116eb
24 changed files with 358 additions and 143 deletions

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ build*/
Thumbs.db Thumbs.db
.vscode/settings.json .vscode/settings.json
third-party/concurrentqueue/benchmarks third-party/concurrentqueue/benchmarks
third-party/concurrentqueue/tests third-party/concurrentqueue/tests
*.bak

View File

@ -1,26 +1,30 @@
Hardware # Hardware Support
=========
Ethernet devices - Ethernet devices
neoVI FIRE 2 - neoVI FIRE 2
CAN 2.0 works - CAN works
RADGalaxy - CAN FD works
CAN 2.0 works - RADGalaxy
- CAN works
STM32 devices - STM32 devices
ValueCAN 4 - ValueCAN 4
CAN 2.0 works - CAN works
neoOBD2 PRO - CAN FD works
CAN 2.0 works - neoOBD2 PRO
neoOBD2-SIM - CAN works
Connects
FTDI devices - FTDI devices
neoVI FIRE - neoVI FIRE
HSCAN works - CAN works
neoVI FIRE 2 - neoVI FIRE 2
CAN 2.0 works - CAN works
ValueCAN 3 - CAN FD works
CAN works - ValueCAN 3
RADStar 2 - CAN works
CAN works - RADStar 2
- CAN works
- neoVI PLASMA
- CAN works
- neoVI ION
- CAN works

13
LICENSE 100644
View File

@ -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.

121
README.md 100644
View File

@ -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 <icsneo/icsneocpp.h>` or the C API with `#include <icsneo/icsneoc.h>`
### 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 <icsneo/icsneoc.h>` 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<icsneo::Device>` 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<icsneo::Message>`. 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<std::shared_ptr<icsneo::Device>> 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<icsneo::Device> 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<std::shared_ptr<icsneo::Message>> 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<icsneo::CANMessage>(msg);
// canmsg->arbid is valid here
// canmsg->data is an std::vector<uint8_t>, 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

View File

@ -17,11 +17,11 @@ APIError::APIError(ErrorType error, const Device* forDevice) : errorStruct({}) {
} }
void APIError::init(ErrorType error) { void APIError::init(ErrorType error) {
timepoint = std::chrono::high_resolution_clock::now(); timepoint = ErrorClock::now();
errorStruct.description = DescriptionForType(error); errorStruct.description = DescriptionForType(error);
errorStruct.errorNumber = (uint32_t)error; errorStruct.errorNumber = (uint32_t)error;
errorStruct.severity = (uint8_t)SeverityForType(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 { std::string APIError::describe() const noexcept {
@ -35,10 +35,10 @@ std::string APIError::describe() const noexcept {
return ss.str(); return ss.str();
} }
bool APIError::isForDevice(std::string serial) const noexcept { bool APIError::isForDevice(std::string filterSerial) const noexcept {
if(!device || serial.length() == 0) if(!device || filterSerial.length() == 0)
return false; return false;
return device->getSerial() == serial; return device->getSerial() == filterSerial;
} }
// API Errors // API Errors

View File

@ -31,8 +31,10 @@ void Communication::spawnThreads() {
} }
void Communication::joinThreads() { void Communication::joinThreads() {
closing = true;
if(readTaskThread.joinable()) if(readTaskThread.joinable())
readTaskThread.join(); readTaskThread.join();
closing = false;
} }
bool Communication::close() { bool Communication::close() {
@ -40,7 +42,6 @@ bool Communication::close() {
return false; return false;
isOpen = false; isOpen = false;
closing = true;
joinThreads(); joinThreads();
return impl->close(); return impl->close();

View File

@ -12,8 +12,10 @@ void MultiChannelCommunication::spawnThreads() {
} }
void MultiChannelCommunication::joinThreads() { void MultiChannelCommunication::joinThreads() {
closing = true;
if(mainChannelReadThread.joinable()) if(mainChannelReadThread.joinable())
mainChannelReadThread.join(); mainChannelReadThread.join();
closing = false;
} }
bool MultiChannelCommunication::sendPacket(std::vector<uint8_t>& bytes) { bool MultiChannelCommunication::sendPacket(std::vector<uint8_t>& bytes) {

View File

@ -180,6 +180,7 @@ bool Device::close() {
if(internalHandlerCallbackID) if(internalHandlerCallbackID)
com->removeMessageCallback(internalHandlerCallbackID); com->removeMessageCallback(internalHandlerCallbackID);
goOffline();
return com->close(); return com->close();
} }

View File

@ -26,6 +26,9 @@ class Device;
class APIError { class APIError {
public: public:
typedef std::chrono::system_clock ErrorClock;
typedef std::chrono::time_point<ErrorClock> ErrorTimePoint;
enum ErrorType : uint32_t { enum ErrorType : uint32_t {
Any = 0, // Used for filtering, should not appear in data Any = 0, // Used for filtering, should not appear in data
@ -73,7 +76,7 @@ public:
Severity getSeverity() const noexcept { return Severity(errorStruct.severity); } Severity getSeverity() const noexcept { return Severity(errorStruct.severity); }
std::string getDescription() const noexcept { return std::string(errorStruct.description); } 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 const Device* getDevice() const noexcept { return device; } // Will return nullptr if this is an API-wide error
std::chrono::time_point<std::chrono::high_resolution_clock> getTimestamp() const noexcept { return timepoint; } ErrorTimePoint getTimestamp() const noexcept { return timepoint; }
bool isForDevice(const Device* forDevice) const noexcept { return forDevice == device; } bool isForDevice(const Device* forDevice) const noexcept { return forDevice == device; }
bool isForDevice(std::string serial) const noexcept; bool isForDevice(std::string serial) const noexcept;
@ -91,7 +94,7 @@ public:
private: private:
neoerror_t errorStruct; neoerror_t errorStruct;
std::string serial; std::string serial;
std::chrono::time_point<std::chrono::high_resolution_clock> timepoint; ErrorTimePoint timepoint;
const Device* device; const Device* device;
void init(ErrorType error); void init(ErrorType error);

View File

@ -26,9 +26,9 @@ public:
get(ret, filter, max); get(ret, filter, max);
return ret; return ret;
} }
void get(std::vector<APIError>& errors, ErrorFilter filter, size_t max = 0) { get(errors, max, filter); } void get(std::vector<APIError>& outErrors, ErrorFilter filter, size_t max = 0) { get(outErrors, max, filter); }
void get(std::vector<APIError>& errors, size_t max = 0, ErrorFilter filter = ErrorFilter()); void get(std::vector<APIError>& outErrors, size_t max = 0, ErrorFilter filter = ErrorFilter());
bool getLastError(APIError& error, ErrorFilter filter = ErrorFilter()); bool getLastError(APIError& outErrors, ErrorFilter filter = ErrorFilter());
void add(APIError error) { void add(APIError error) {
std::lock_guard<std::mutex> lk(mutex); std::lock_guard<std::mutex> lk(mutex);

View File

@ -107,8 +107,8 @@ typedef struct {
uint8_t header[4]; uint8_t header[4];
uint16_t netid; uint16_t netid;
uint8_t type; uint8_t type;
uint8_t reserved[9]; uint8_t reserved[17];
} neomessage_t; } neomessage_t; // 64 bytes total
// Any time you add another neomessage_*_t type, make sure to add it to the static_asserts below! // Any time you add another neomessage_*_t type, make sure to add it to the static_asserts below!
typedef struct { typedef struct {
@ -120,7 +120,7 @@ typedef struct {
uint16_t netid; uint16_t netid;
uint8_t type; uint8_t type;
uint8_t dlcOnWire; uint8_t dlcOnWire;
char reserved[8]; char reserved[16];
} neomessage_can_t; } neomessage_can_t;
#pragma pack(pop) #pragma pack(pop)

View File

@ -142,6 +142,8 @@ public:
return "Other"; return "Other";
case Type::Internal: case Type::Internal:
return "Internal"; return "Internal";
case Type::Ethernet:
return "Ethernet";
case Type::Invalid: case Type::Invalid:
default: default:
return "Invalid Type"; return "Invalid Type";
@ -187,6 +189,21 @@ public:
case NetID::Invalid: case NetID::Invalid:
case NetID::Any: case NetID::Any:
return Type::Invalid; 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: default:
return Type::Other; return Type::Other;
} }

View File

@ -103,29 +103,29 @@ protected:
template<typename Transport> template<typename Transport>
std::unique_ptr<ICommunication> makeTransport() { return std::unique_ptr<ICommunication>(new Transport(err, getWritableNeoDevice())); } std::unique_ptr<ICommunication> makeTransport() { return std::unique_ptr<ICommunication>(new Transport(err, getWritableNeoDevice())); }
virtual void setupTransport(ICommunication* transport) {} virtual void setupTransport(ICommunication* stransport) { (void)stransport; }
virtual std::shared_ptr<Packetizer> makePacketizer() { return std::make_shared<Packetizer>(err); } virtual std::shared_ptr<Packetizer> makePacketizer() { return std::make_shared<Packetizer>(err); }
virtual void setupPacketizer(Packetizer* packetizer) {} virtual void setupPacketizer(Packetizer* spacketizer) { (void)spacketizer; }
virtual std::unique_ptr<Encoder> makeEncoder(std::shared_ptr<Packetizer> p) { return std::unique_ptr<Encoder>(new Encoder(err, p)); } virtual std::unique_ptr<Encoder> makeEncoder(std::shared_ptr<Packetizer> p) { return std::unique_ptr<Encoder>(new Encoder(err, p)); }
virtual void setupEncoder(Encoder* encoder) {} virtual void setupEncoder(Encoder* sencoder) { (void)sencoder; }
virtual std::unique_ptr<Decoder> makeDecoder() { return std::unique_ptr<Decoder>(new Decoder(err)); } virtual std::unique_ptr<Decoder> makeDecoder() { return std::unique_ptr<Decoder>(new Decoder(err)); }
virtual void setupDecoder(Decoder* decoder) {} virtual void setupDecoder(Decoder* sdecoder) { (void)sdecoder; }
virtual std::shared_ptr<Communication> makeCommunication( virtual std::shared_ptr<Communication> makeCommunication(
std::unique_ptr<ICommunication> t, std::unique_ptr<ICommunication> t,
std::shared_ptr<Packetizer> p, std::shared_ptr<Packetizer> p,
std::unique_ptr<Encoder> e, std::unique_ptr<Encoder> e,
std::unique_ptr<Decoder> d) { return std::make_shared<Communication>(err, std::move(t), p, std::move(e), std::move(d)); } std::unique_ptr<Decoder> d) { return std::make_shared<Communication>(err, std::move(t), p, std::move(e), std::move(d)); }
virtual void setupCommunication(Communication* com) {} virtual void setupCommunication(Communication* scom) { (void)scom; }
template<typename Settings> template<typename Settings>
std::unique_ptr<IDeviceSettings> makeSettings(std::shared_ptr<Communication> com) { std::unique_ptr<IDeviceSettings> makeSettings(std::shared_ptr<Communication> com) {
return std::unique_ptr<IDeviceSettings>(new Settings(com)); return std::unique_ptr<IDeviceSettings>(new Settings(com));
} }
virtual void setupSettings(IDeviceSettings* settings) {} virtual void setupSettings(IDeviceSettings* ssettings) { (void)ssettings; }
// END Initialization Functions // END Initialization Functions
void handleInternalMessage(std::shared_ptr<Message> message); void handleInternalMessage(std::shared_ptr<Message> message);

View File

@ -15,13 +15,12 @@ public:
std::vector<std::shared_ptr<Device>> found; std::vector<std::shared_ptr<Device>> found;
for(auto& foundDev : PCAP::FindAll()) { for(auto& foundDev : PCAP::FindAll()) {
auto packetizer = std::make_shared<Packetizer>(); auto fakedev = std::shared_ptr<NeoVIFIRE2ETH>(new NeoVIFIRE2ETH({}));
auto decoder = std::unique_ptr<Decoder>(new Decoder()); for (auto& payload : foundDev.discoveryPackets)
for(auto& payload : foundDev.discoveryPackets) fakedev->com->packetizer->input(payload);
packetizer->input(payload); for (auto& packet : fakedev->com->packetizer->output()) {
for(auto& packet : packetizer->output()) {
std::shared_ptr<Message> msg; std::shared_ptr<Message> msg;
if(!decoder->decode(msg, packet)) if (!fakedev->com->decoder->decode(msg, packet))
continue; // We failed to decode this packet continue; // We failed to decode this packet
if(!msg || msg->network.getNetID() != Network::NetID::Main51) if(!msg || msg->network.getNetID() != Network::NetID::Main51)
@ -44,17 +43,16 @@ public:
return found; 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) { NeoVIFIRE2ETH(neodevice_t neodevice) : NeoVIFIRE2(neodevice) {
initialize<PCAP, NeoVIFIRE2Settings>(); initialize<PCAP, NeoVIFIRE2Settings>();
productId = PRODUCT_ID; 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;
}
}; };
} }

View File

@ -47,18 +47,17 @@ public:
return found; return found;
} }
protected:
void setupPacketizer(Packetizer* packetizer) override {
packetizer->disableChecksum = true;
packetizer->align16bit = false;
}
private:
RADGalaxy(neodevice_t neodevice) : Device(neodevice) { RADGalaxy(neodevice_t neodevice) : Device(neodevice) {
initialize<PCAP>(); initialize<PCAP>();
getWritableNeoDevice().type = DEVICE_TYPE; getWritableNeoDevice().type = DEVICE_TYPE;
productId = PRODUCT_ID; productId = PRODUCT_ID;
} }
protected:
void setupPacketizer(Packetizer* packetizer) override {
packetizer->disableChecksum = true;
packetizer->align16bit = false;
}
}; };
} }

View File

@ -43,7 +43,6 @@ public:
return found; return found;
} }
private:
RADStar2ETH(neodevice_t neodevice) : RADStar2(neodevice) { RADStar2ETH(neodevice_t neodevice) : RADStar2(neodevice) {
initialize<PCAP>(); initialize<PCAP>();
} }

View File

@ -8,7 +8,7 @@ namespace icsneo {
class FTDI : public VCP { class FTDI : public VCP {
public: public:
FTDI(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {} FTDI(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, L"serenum"); } static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, { L"serenum", L"ftdibus" }); }
}; };
} }

View File

@ -27,6 +27,7 @@ public:
bool close(); bool close();
private: private:
PCAPDLL pcap; PCAPDLL pcap;
device_errorhandler_t err;
char errbuf[PCAP_ERRBUF_SIZE] = { 0 }; char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
neodevice_t& device; neodevice_t& device;
uint8_t deviceMAC[6]; uint8_t deviceMAC[6];

View File

@ -8,7 +8,7 @@ namespace icsneo {
class STM32 : public VCP { class STM32 : public VCP {
public: public:
STM32(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {} STM32(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, L"usbser"); } static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, { L"usbser" }); }
}; };
} }

View File

@ -16,7 +16,7 @@ namespace icsneo {
// Virtual COM Port Communication // Virtual COM Port Communication
class VCP : public ICommunication { class VCP : public ICommunication {
public: public:
static std::vector<neodevice_t> FindByProduct(int product, wchar_t* driverName); static std::vector<neodevice_t> FindByProduct(int product, std::vector<std::wstring> driverName);
static bool IsHandleValid(neodevice_handle_t handle); static bool IsHandleValid(neodevice_handle_t handle);
typedef void(*fn_boolCallback)(bool success); typedef void(*fn_boolCallback)(bool success);

View File

@ -97,10 +97,18 @@ bool FTDI::close() {
if(writeThread.joinable()) if(writeThread.joinable())
writeThread.join(); writeThread.join();
if(ftdiDevice.close()) closing = false;
return false;
return true; bool ret = true;
if(ftdiDevice.close())
ret = false;
uint8_t flush;
WriteOperation flushop;
while(readQueue.try_dequeue(flush)) {}
while(writeQueue.try_dequeue(flushop)) {}
return ret;
} }
void FTDI::readTask() { void FTDI::readTask() {

View File

@ -252,8 +252,15 @@ bool STM32::close() {
if(writeThread.joinable()) if(writeThread.joinable())
writeThread.join(); writeThread.join();
closing = false;
int ret = ::close(fd); int ret = ::close(fd);
fd = -1; fd = -1;
uint8_t flush;
WriteOperation flushop;
while (readQueue.try_dequeue(flush)) {}
while (writeQueue.try_dequeue(flushop)) {}
return ret == 0; return ret == 0;
} }

View File

@ -228,10 +228,16 @@ bool PCAP::close() {
closing = true; // Signal the threads that we are closing closing = true; // Signal the threads that we are closing
readThread.join(); readThread.join();
writeThread.join(); writeThread.join();
closing = false;
pcap.close(interface.fp); pcap.close(interface.fp);
interface.fp = nullptr; interface.fp = nullptr;
uint8_t flush;
WriteOperation flushop;
while(readQueue.try_dequeue(flush)) {}
while(writeQueue.try_dequeue(flushop)) {}
return true; return true;
} }

View File

@ -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_TIMES = 5;
static constexpr unsigned int RETRY_DELAY = 50; static constexpr unsigned int RETRY_DELAY = 50;
std::vector<neodevice_t> VCP::FindByProduct(int product, wchar_t* driverName) { std::vector<neodevice_t> VCP::FindByProduct(int product, std::vector<std::wstring> driverNames) {
std::vector<neodevice_t> found; std::vector<neodevice_t> found;
std::wstringstream regss; for(auto& driverName : driverNames) {
regss << DRIVER_SERVICES_REG_KEY << driverName << L"\\Enum\\"; std::wstringstream regss;
std::wstring driverEnumRegKey = regss.str(); regss << DRIVER_SERVICES_REG_KEY << driverName << L"\\Enum\\";
std::wstring driverEnumRegKey = regss.str();
uint32_t deviceCount = 0; uint32_t deviceCount = 0;
if(!Registry::Get(driverEnumRegKey, L"Count", deviceCount)) { if(!Registry::Get(driverEnumRegKey, L"Count", deviceCount)) {
return found; 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; for(uint32_t i = 0; i < deviceCount; i++) {
if(!sn || conversionError) { neodevice_t device = {};
// 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()); // 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 std::transform(entry.begin(), entry.end(), entry.begin(), std::towupper);
// This will be stored under ALL_ENUM_REG_KEY\entry\Device Parameters\PortName (entry from the FTDI_ENUM)
std::wstringstream dpss; std::wstringstream vss;
dpss << ALL_ENUM_REG_KEY << entry << L"\\Device Parameters"; vss << "VID_" << std::setfill(L'0') << std::setw(4) << std::uppercase << std::hex << INTREPID_USB_VENDOR_ID; // Intrepid Vendor ID
std::wstring port; if(entry.find(vss.str()) == std::wstring::npos)
Registry::Get(dpss.str(), L"PortName", port); // TODO If error do something else (Plasma maybe?) continue;
std::transform(port.begin(), port.end(), port.begin(), std::towupper);
auto compos = port.find(L"COM"); std::wstringstream pss;
device.handle = 0; pss << "PID_" << std::setfill(L'0') << std::setw(4) << std::uppercase << std::hex << product;
if(compos != std::wstring::npos) { 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 { try {
device.handle = std::stoi(port.substr(compos + 3)); sn = std::stoi(entry.substr(startchar + 1));
} catch(...) {} // In case of this, or any other error, handle has already been initialized to 0 }
} 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; return found;
@ -113,8 +130,10 @@ bool VCP::open(bool fromAsync) {
if(isOpen() || (!fromAsync && opening)) if(isOpen() || (!fromAsync && opening))
return false; return false;
if(!IsHandleValid(device.handle)) if(!IsHandleValid(device.handle)) {
err(APIError::DriverFailedToOpen);
return false; return false;
}
opening = true; opening = true;
@ -132,8 +151,10 @@ bool VCP::open(bool fromAsync) {
opening = false; opening = false;
if(!isOpen()) if(!isOpen()) {
err(APIError::DriverFailedToOpen);
return false; return false;
}
// Set the timeouts // Set the timeouts
COMMTIMEOUTS timeouts; COMMTIMEOUTS timeouts;
@ -151,6 +172,7 @@ bool VCP::open(bool fromAsync) {
if(!SetCommTimeouts(handle, &timeouts)) { if(!SetCommTimeouts(handle, &timeouts)) {
close(); close();
err(APIError::DriverFailedToOpen);
return false; return false;
} }
@ -158,6 +180,7 @@ bool VCP::open(bool fromAsync) {
DCB comstate; DCB comstate;
if(!GetCommState(handle, &comstate)) { if(!GetCommState(handle, &comstate)) {
close(); close();
err(APIError::DriverFailedToOpen);
return false; return false;
} }
@ -170,6 +193,7 @@ bool VCP::open(bool fromAsync) {
if(!SetCommState(handle, &comstate)) { if(!SetCommState(handle, &comstate)) {
close(); close();
err(APIError::DriverFailedToOpen);
return false; return false;
} }
@ -181,12 +205,14 @@ bool VCP::open(bool fromAsync) {
overlappedWait.hEvent = CreateEvent(nullptr, true, false, nullptr); overlappedWait.hEvent = CreateEvent(nullptr, true, false, nullptr);
if (overlappedRead.hEvent == nullptr || overlappedWrite.hEvent == nullptr || overlappedWait.hEvent == nullptr) { if (overlappedRead.hEvent == nullptr || overlappedWrite.hEvent == nullptr || overlappedWait.hEvent == nullptr) {
close(); close();
err(APIError::DriverFailedToOpen);
return false; return false;
} }
// Set up event so that we will satisfy overlappedWait when a character comes in // Set up event so that we will satisfy overlappedWait when a character comes in
if(!SetCommMask(handle, EV_RXCHAR)) { if(!SetCommMask(handle, EV_RXCHAR)) {
close(); close();
err(APIError::DriverFailedToOpen);
return false; return false;
} }
@ -214,6 +240,7 @@ bool VCP::close() {
t->join(); // Wait for the threads to close t->join(); // Wait for the threads to close
readThread.join(); readThread.join();
writeThread.join(); writeThread.join();
closing = false;
if(!CloseHandle(handle)) if(!CloseHandle(handle))
return false; return false;
@ -224,16 +251,24 @@ bool VCP::close() {
if(overlappedRead.hEvent != INVALID_HANDLE_VALUE) { if(overlappedRead.hEvent != INVALID_HANDLE_VALUE) {
if(!CloseHandle(overlappedRead.hEvent)) if(!CloseHandle(overlappedRead.hEvent))
ret = false; ret = false;
overlappedRead.hEvent = INVALID_HANDLE_VALUE;
} }
if(overlappedWrite.hEvent != INVALID_HANDLE_VALUE) { if(overlappedWrite.hEvent != INVALID_HANDLE_VALUE) {
if(!CloseHandle(overlappedWrite.hEvent)) if(!CloseHandle(overlappedWrite.hEvent))
ret = false; ret = false;
overlappedWrite.hEvent = INVALID_HANDLE_VALUE;
} }
if(overlappedWait.hEvent != INVALID_HANDLE_VALUE) { if(overlappedWait.hEvent != INVALID_HANDLE_VALUE) {
if(!CloseHandle(overlappedWait.hEvent)) if(!CloseHandle(overlappedWait.hEvent))
ret = false; ret = false;
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 // 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; return ret;
@ -270,7 +305,6 @@ void VCP::readTask() {
case WAIT: { case WAIT: {
auto ret = WaitForSingleObject(overlappedRead.hEvent, 100); auto ret = WaitForSingleObject(overlappedRead.hEvent, 100);
if(ret == WAIT_OBJECT_0) { if(ret == WAIT_OBJECT_0) {
auto err = GetLastError();
if(GetOverlappedResult(handle, &overlappedRead, &bytesRead, FALSE)) { if(GetOverlappedResult(handle, &overlappedRead, &bytesRead, FALSE)) {
readQueue.enqueue_bulk(readbuf, bytesRead); readQueue.enqueue_bulk(readbuf, bytesRead);
state = LAUNCH; state = LAUNCH;
@ -300,8 +334,8 @@ void VCP::writeTask() {
if(WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &overlappedWrite)) if(WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &overlappedWrite))
continue; continue;
auto err = GetLastError(); auto winerr = GetLastError();
if(err == ERROR_IO_PENDING) { if(winerr == ERROR_IO_PENDING) {
state = WAIT; state = WAIT;
} }
else else