From 9c51913252b7b9e7e34b0e2bebe761b493662356 Mon Sep 17 00:00:00 2001 From: Ben Evans Date: Sat, 11 May 2024 20:31:32 +1000 Subject: [PATCH 1/4] Add candle_api.dll for gs_usb support on Windows --- README.md | 3 ++ libs/candle_api/LICENSE | 68 ++++++++++++++++++++++++++++ libs/candle_api/README.md | 8 ++++ libs/candle_api/candle_api.dll | Bin 0 -> 74220 bytes libs/candle_api/libcandle_api.dll.a | Bin 0 -> 17582 bytes 5 files changed, 79 insertions(+) create mode 100644 libs/candle_api/LICENSE create mode 100644 libs/candle_api/README.md create mode 100644 libs/candle_api/candle_api.dll create mode 100644 libs/candle_api/libcandle_api.dll.a diff --git a/README.md b/README.md index e6f6aa1a..901d4982 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ of this program. It can load and save in several formats: Now this code does not depend on anything other than what is in the source tree or available from the Qt installer. +For connecting to gs_usb based devices on Windows, this application dynamically links to +candle_api.dll which is licensed under the LGPL-3.0. The source for this DLL is available [here](https://github.com/BennyEvans/candle_dll) + Uses QCustomPlot available at: http://www.qcustomplot.com/ diff --git a/libs/candle_api/LICENSE b/libs/candle_api/LICENSE new file mode 100644 index 00000000..a57fbf38 --- /dev/null +++ b/libs/candle_api/LICENSE @@ -0,0 +1,68 @@ +CANDLE_API LICENSE (candle_api.dll) + +GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. +0. Additional Definitions. + +As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. + +“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. + +The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. +1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. +2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + + a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or + b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. + b) Accompany the object code with a copy of the GNU GPL and this license document. + +4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. + b) Accompany the Combined Work with a copy of the GNU GPL and this license document. + c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. + d) Do one of the following: + 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. + e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) + +5. Combined Libraries. + +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. + b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + diff --git a/libs/candle_api/README.md b/libs/candle_api/README.md new file mode 100644 index 00000000..68188b28 --- /dev/null +++ b/libs/candle_api/README.md @@ -0,0 +1,8 @@ +# CANDLE_API +The source used to compile candle_api.dll is available at [https://github.com/BennyEvans/candle_dll](https://github.com/BennyEvans/candle_dll) commit [f9ee85e] (https://github.com/BennyEvans/candle_dll/commit/f9ee85e830be93c1d76a5102a8cb4f79e2719259) + +It was compiled with the following commands: +``` +cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -B ./build . +cmake --build ./build +``` \ No newline at end of file diff --git a/libs/candle_api/candle_api.dll b/libs/candle_api/candle_api.dll new file mode 100644 index 0000000000000000000000000000000000000000..f3bcf3ec932376eae1be0e81b39bcf150ad7499e GIT binary patch literal 74220 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 literal 0 HcmV?d00001 From ebdd376952d8f30d98c856ba54d4c89c68f1a59b Mon Sep 17 00:00:00 2001 From: Ben Evans Date: Sun, 12 May 2024 13:12:14 +1000 Subject: [PATCH 2/4] Add getter for serial speed so that reset connection works correctly --- connections/canconnection.cpp | 4 ++++ connections/canconnection.h | 5 +++++ connections/connectionwindow.cpp | 23 +++++++++++++++++------ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/connections/canconnection.cpp b/connections/canconnection.cpp index 9eac8176..e94775e1 100644 --- a/connections/canconnection.cpp +++ b/connections/canconnection.cpp @@ -251,6 +251,10 @@ QString CANConnection::getDriver() return mDriver; } +int CANConnection::getSerialSpeed(){ + return mSerialSpeed; +} + LFQueue& CANConnection::getQueue() { return mQueue; } diff --git a/connections/canconnection.h b/connections/canconnection.h index fde82961..6295ccb1 100644 --- a/connections/canconnection.h +++ b/connections/canconnection.h @@ -64,6 +64,11 @@ class CANConnection : public QObject */ QString getDriver(); + /** + * @brief getSerialSpeed + * @return returns the configured serial speed of the device + */ + int getSerialSpeed(); /** * @brief getQueue diff --git a/connections/connectionwindow.cpp b/connections/connectionwindow.cpp index 5e4df7aa..e04eab7a 100644 --- a/connections/connectionwindow.cpp +++ b/connections/connectionwindow.cpp @@ -301,8 +301,9 @@ void ConnectionWindow::handleResetConn() { QString port, driver; CANCon::type type; - int serSpeed, busSpeed, dataRate; + int serSpeed, busSpeed, samplePoint, dataRate; bool canFd; + int offset = ui->tabBuses->currentIndex(); int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); if (selIdx <0) return; @@ -315,11 +316,21 @@ void ConnectionWindow::handleResetConn() type = conn_p->getType(); port = conn_p->getPort(); driver = conn_p->getDriver(); - serSpeed = 0; //TODO: implement these - busSpeed = 0; - dataRate = 0; - canFd = false; - + serSpeed = conn_p->getSerialSpeed(); + + CANBus bus; + if (conn_p->getBusSettings(offset, bus)) { + busSpeed = bus.getSpeed(); + samplePoint = bus.getSamplePoint(); + dataRate = bus.getDataRate(); + canFd = bus.isCanFD(); + } else { + qDebug() << "Could not retrieve bus settings!"; + busSpeed = 0; + samplePoint = 0; + dataRate = 0; + canFd = false; + } /* stop and delete connection */ conn_p->stop(); From cbbabf5c48b2813a75bd81d9b14a87acc9034124 Mon Sep 17 00:00:00 2001 From: Ben Evans Date: Sun, 12 May 2024 13:19:06 +1000 Subject: [PATCH 3/4] Add sample point property to can bus object. --- connections/canbus.cpp | 12 ++++++++++++ connections/canbus.h | 3 +++ connections/canconfactory.cpp | 2 +- connections/canconfactory.h | 2 +- connections/canconnection.cpp | 5 +++++ connections/canconnection.h | 2 ++ connections/canlogserver.cpp | 2 +- connections/canserver.cpp | 2 +- connections/connectionwindow.cpp | 12 +++++++----- connections/connectionwindow.h | 2 +- connections/gvretserial.cpp | 2 +- connections/lawicel_serial.cpp | 2 +- connections/mqtt_bus.cpp | 2 +- connections/newconnectiondialog.h | 1 + connections/serialbusconnection.cpp | 2 +- connections/socketcand.cpp | 2 +- 16 files changed, 40 insertions(+), 15 deletions(-) diff --git a/connections/canbus.cpp b/connections/canbus.cpp index 0623baa8..20e06b42 100644 --- a/connections/canbus.cpp +++ b/connections/canbus.cpp @@ -5,6 +5,7 @@ CANBus::CANBus() { speed = 500000; + samplePoint = 875; listenOnly = false; singleWire = false; active = false; @@ -15,6 +16,7 @@ CANBus::CANBus() bool CANBus::operator==(const CANBus& bus) const{ return speed == bus.speed && + samplePoint == bus.samplePoint && listenOnly == bus.listenOnly && singleWire == bus.singleWire && active == bus.active && @@ -27,6 +29,10 @@ void CANBus::setSpeed(int newSpeed){ speed = newSpeed; } +void CANBus::setSamplePoint(int newSamplePoint){ + samplePoint = newSamplePoint; +} + void CANBus::setListenOnly(bool mode){ //qDebug() << "CANBUS SetListenOnly = " << mode; listenOnly = mode; @@ -51,6 +57,10 @@ int CANBus::getSpeed() const { return speed; } +int CANBus::getSamplePoint() const { + return samplePoint; +} + int CANBus::getDataRate() const { return dataRate; } @@ -80,6 +90,7 @@ bool CANBus::isCanFD() const { QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus) { pStream << pCanBus.speed; + pStream << pCanBus.samplePoint; pStream << pCanBus.listenOnly; pStream << pCanBus.singleWire; pStream << pCanBus.active; @@ -90,6 +101,7 @@ QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus) QDataStream & operator>>(QDataStream & pStream, CANBus& pCanBus) { pStream >> pCanBus.speed; + pStream >> pCanBus.samplePoint; pStream >> pCanBus.listenOnly; pStream >> pCanBus.singleWire; pStream >> pCanBus.active; diff --git a/connections/canbus.h b/connections/canbus.h index 61fc122b..91382063 100644 --- a/connections/canbus.h +++ b/connections/canbus.h @@ -6,6 +6,7 @@ class CANBus { int speed; + int samplePoint; bool listenOnly; bool singleWire; bool active; //is this bus turned on? @@ -20,6 +21,7 @@ class CANBus bool operator==(const CANBus&) const; void setSpeed(int); // new speed + void setSamplePoint(int); void setListenOnly(bool); //bool for whether to only listen void setSingleWire(bool); //bool for whether to use single wire mode void setActive(bool); //whether this bus should be enabled or not. @@ -27,6 +29,7 @@ class CANBus void setDataRate(int newSpeed); int getSpeed() const; + int getSamplePoint() const; int getDataRate() const; bool isListenOnly() const; bool isSingleWire() const; diff --git a/connections/canconfactory.cpp b/connections/canconfactory.cpp index ae2bc6b0..74234801 100644 --- a/connections/canconfactory.cpp +++ b/connections/canconfactory.cpp @@ -10,7 +10,7 @@ using namespace CANCon; -CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate) +CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate, int pSamplePoint) { switch(pType) { case SERIALBUS: diff --git a/connections/canconfactory.h b/connections/canconfactory.h index ecf453d3..14551ae3 100644 --- a/connections/canconfactory.h +++ b/connections/canconfactory.h @@ -7,7 +7,7 @@ class CanConFactory { public: - static CANConnection* create(CANCon::type, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate); + static CANConnection* create(CANCon::type, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate, int pSamplePoint); }; #endif // CANCONFACTORY_H diff --git a/connections/canconnection.cpp b/connections/canconnection.cpp index e94775e1..514ce3c5 100644 --- a/connections/canconnection.cpp +++ b/connections/canconnection.cpp @@ -7,6 +7,7 @@ CANConnection::CANConnection(QString pPort, CANCon::type pType, int pSerialSpeed, int pBusSpeed, + int pSamplePoint, bool pCanFd, int pDataRate, int pNumBuses, @@ -40,6 +41,10 @@ CANConnection::CANConnection(QString pPort, } if (pBusSpeed > 0) mBusData[0].mBus.setSpeed(pBusSpeed); + if ((pSamplePoint >= 0) && (pSamplePoint <= 1000)) { + mBusData[0].mBus.setSamplePoint(pSamplePoint); + } + mBusData[0].mBus.setCanFD(pCanFd); if (pDataRate > 0) mBusData[0].mBus.setDataRate(pDataRate); diff --git a/connections/canconnection.h b/connections/canconnection.h index 6295ccb1..c0fcd5bc 100644 --- a/connections/canconnection.h +++ b/connections/canconnection.h @@ -23,6 +23,7 @@ class CANConnection : public QObject * @param pType: the type of connection @ref CANCon::type * @param pSerialSpeed: for devices with variable serial speed this is that speed. * @param pBusSpeed: set an initial speed when opening this connection + * @param pSamplePoint: set if the device supports sample point configuration * @param pNumBuses: the number of buses the device has * @param pQueueLen: the length of the lock free queue to use * @param pUseThread: if set to true, object will be execute in a dedicated thread @@ -32,6 +33,7 @@ class CANConnection : public QObject CANCon::type pType, int pSerialSpeed, int pBusSpeed, + int pSamplePoint, bool pCanFd, int pDataRate, int pNumBuses, diff --git a/connections/canlogserver.cpp b/connections/canlogserver.cpp index 78a2bc5f..4d578652 100644 --- a/connections/canlogserver.cpp +++ b/connections/canlogserver.cpp @@ -9,7 +9,7 @@ #include "canlogserver.h" CanLogServer::CanLogServer(QString serverAddressString) : - CANConnection(serverAddressString, "CanLogserver", CANCon::CANLOGSERVER, 0, 0, false, 0, 1, 4000, true), + CANConnection(serverAddressString, "CanLogserver", CANCon::CANLOGSERVER, 0, 0, 0, false, 0, 1, 4000, true), m_ptcpSocket(new QTcpSocket(this)) { diff --git a/connections/canserver.cpp b/connections/canserver.cpp index a8ad1454..a66043db 100644 --- a/connections/canserver.cpp +++ b/connections/canserver.cpp @@ -16,7 +16,7 @@ #include "canserver.h" CANserver::CANserver(QString serverAddressString) : - CANConnection(serverAddressString, "CANserver", CANCon::CANSERVER, 0, 0, false, 0, 3, 4000, true), + CANConnection(serverAddressString, "CANserver", CANCon::CANSERVER, 0, 0, 0, false, 0, 3, 4000, true), _udpClient(new QUdpSocket(this)), _heartbeatTimer(new QTimer(this)) { diff --git a/connections/connectionwindow.cpp b/connections/connectionwindow.cpp index e04eab7a..29ed1880 100644 --- a/connections/connectionwindow.cpp +++ b/connections/connectionwindow.cpp @@ -253,6 +253,7 @@ void ConnectionWindow::handleNewConn() QString newDriver; int newSerialSpeed; int newBusSpeed; + int newSamplePoint; bool newCanFd; int newDataRate; CANConnection *conn; @@ -264,9 +265,10 @@ void ConnectionWindow::handleNewConn() newDriver = thisDialog->getDriverName(); newSerialSpeed = thisDialog->getSerialSpeed(); newBusSpeed = thisDialog->getBusSpeed(); + newSamplePoint = thisDialog->getSamplePoint(); newCanFd=thisDialog->isCanFd(); newDataRate = thisDialog->getDataRate(); - conn = create(newType, newPort, newDriver, newSerialSpeed, newBusSpeed, newCanFd, newDataRate); + conn = create(newType, newPort, newDriver, newSerialSpeed, newBusSpeed, newCanFd, newDataRate, newSamplePoint); if (conn) { connModel->add(conn); @@ -337,7 +339,7 @@ void ConnectionWindow::handleResetConn() conn_p = nullptr; - conn_p = create(type, port, driver, serSpeed, busSpeed,canFd,dataRate); + conn_p = create(type, port, driver, serSpeed, busSpeed, canFd, dataRate, samplePoint); if (conn_p) connModel->replace(selIdx, conn_p); } @@ -526,12 +528,12 @@ void ConnectionWindow::handleSendText() { emit sendDebugData(bytes); } -CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate) +CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate, int pSamplePoint) { CANConnection* conn_p; /* create connection */ - conn_p = CanConFactory::create(pTye, pPortName, pDriver, pSerialSpeed, pBusSpeed, pCanFd, pDataRate); + conn_p = CanConFactory::create(pTye, pPortName, pDriver, pSerialSpeed, pBusSpeed, pCanFd, pDataRate, pSamplePoint); if(conn_p) { /* connect signal */ @@ -568,7 +570,7 @@ void ConnectionWindow::loadConnections() for(int i = 0 ; i < portNames.count() ; i++) { //TODO: add serial speed and bus speed to this properly. - CANConnection* conn_p = create((CANCon::type)devTypes[i], portNames[i], driverNames[i], 0, 0, false, 0); + CANConnection* conn_p = create((CANCon::type)devTypes[i], portNames[i], driverNames[i], 0, 0, false, 0, 875); /* add connection to model */ connModel->add(conn_p); } diff --git a/connections/connectionwindow.h b/connections/connectionwindow.h index 61a94c5e..8f629b83 100644 --- a/connections/connectionwindow.h +++ b/connections/connectionwindow.h @@ -65,7 +65,7 @@ private slots: QVector remoteDeviceIPGVRET; QVector remoteDeviceKayak; - CANConnection* create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate); + CANConnection* create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate, int pSamplePoint); void populateBusDetails(int offset); void loadConnections(); void saveConnections(); diff --git a/connections/gvretserial.cpp b/connections/gvretserial.cpp index dc63ae01..09172a6d 100644 --- a/connections/gvretserial.cpp +++ b/connections/gvretserial.cpp @@ -9,7 +9,7 @@ #include "gvretserial.h" GVRetSerial::GVRetSerial(QString portName, bool useTcp) : - CANConnection(portName, "gvret", CANCon::GVRET_SERIAL, 0, 0, false, 0, 3, 4000, true), + CANConnection(portName, "gvret", CANCon::GVRET_SERIAL, 0, 0, 0, false, 0, 3, 4000, true), mTimer(this), /*NB: set this as parent of timer to manage it from working thread */ useTcp(useTcp) { diff --git a/connections/lawicel_serial.cpp b/connections/lawicel_serial.cpp index ddf9e4dd..5a65a0eb 100644 --- a/connections/lawicel_serial.cpp +++ b/connections/lawicel_serial.cpp @@ -10,7 +10,7 @@ #include "utility.h" LAWICELSerial::LAWICELSerial(QString portName, int serialSpeed, int lawicelSpeed, bool canFd, int dataRate) : - CANConnection(portName, "LAWICEL", CANCon::LAWICEL,serialSpeed, lawicelSpeed, canFd, dataRate, 3, 4000, true), + CANConnection(portName, "LAWICEL", CANCon::LAWICEL,serialSpeed, lawicelSpeed, 0, canFd, dataRate, 3, 4000, true), mTimer(this) /*NB: set this as parent of timer to manage it from working thread */ { sendDebug("LAWICELSerial()"); diff --git a/connections/mqtt_bus.cpp b/connections/mqtt_bus.cpp index 4220e348..a37a3ea2 100644 --- a/connections/mqtt_bus.cpp +++ b/connections/mqtt_bus.cpp @@ -9,7 +9,7 @@ #include "mqtt_bus.h" MQTT_BUS::MQTT_BUS(QString topicName) : - CANConnection(topicName, "mqtt_client", CANCon::MQTT, 0, 0, false, 0, 1, 4000, true), + CANConnection(topicName, "mqtt_client", CANCon::MQTT, 0, 0, 0, false, 0, 1, 4000, true), mTimer(this) /*NB: set this as parent of timer to manage it from working thread */ { diff --git a/connections/newconnectiondialog.h b/connections/newconnectiondialog.h index 387a4fb5..66512af1 100644 --- a/connections/newconnectiondialog.h +++ b/connections/newconnectiondialog.h @@ -26,6 +26,7 @@ class NewConnectionDialog : public QDialog QString getDriverName(); int getSerialSpeed(); int getBusSpeed(); + int getSamplePoint(); bool isCanFd(); int getDataRate(); diff --git a/connections/serialbusconnection.cpp b/connections/serialbusconnection.cpp index 8ebebbfc..4ed63b89 100644 --- a/connections/serialbusconnection.cpp +++ b/connections/serialbusconnection.cpp @@ -12,7 +12,7 @@ /***********************************/ SerialBusConnection::SerialBusConnection(QString portName, QString driverName) : - CANConnection(portName, driverName, CANCon::SERIALBUS,0 ,0, false, 0 ,1, 4000, true), + CANConnection(portName, driverName, CANCon::SERIALBUS,0 ,0, 0, false, 0 ,1, 4000, true), mTimer(this) /*NB: set connection as parent of timer to manage it from working thread */ { } diff --git a/connections/socketcand.cpp b/connections/socketcand.cpp index 6ef89755..e1a282a8 100644 --- a/connections/socketcand.cpp +++ b/connections/socketcand.cpp @@ -10,7 +10,7 @@ #include "socketcand.h" SocketCANd::SocketCANd(QString portName) : - CANConnection(portName, "kayak", CANCon::KAYAK, 0, 0, false, 0, 1, 4000, true), + CANConnection(portName, "kayak", CANCon::KAYAK, 0, 0, 0, false, 0, 1, 4000, true), mTimer(this) /*NB: set this as parent of timer to manage it from working thread */ { From a69cbebb33d51219caf38a85933d579471db32d9 Mon Sep 17 00:00:00 2001 From: Ben Evans Date: Tue, 14 May 2024 07:21:14 +1000 Subject: [PATCH 4/4] Add support for GS_USB based devices --- README.md | 4 + SavvyCAN.pro | 3 + connections/canconconst.h | 1 + connections/canconfactory.cpp | 13 + connections/canconnectionmodel.cpp | 1 + connections/connectionwindow.cpp | 83 +++- connections/connectionwindow.h | 2 + connections/gs_usb_driver/gs_usb.cpp | 467 ++++++++++++++++++ connections/gs_usb_driver/gs_usb.h | 87 ++++ connections/gs_usb_driver/gs_usb.pri | 11 + .../gs_usb_driver/gs_usb_definitions.h | 155 ++++++ connections/gs_usb_driver/gs_usb_functions.h | 79 +++ connections/gs_usb_driver/gs_usb_reader.cpp | 76 +++ connections/gs_usb_driver/gs_usb_reader.h | 53 ++ connections/newconnectiondialog.cpp | 145 +++++- connections/newconnectiondialog.h | 6 +- ui/newconnectiondialog.ui | 53 +- 17 files changed, 1182 insertions(+), 57 deletions(-) create mode 100644 connections/gs_usb_driver/gs_usb.cpp create mode 100644 connections/gs_usb_driver/gs_usb.h create mode 100644 connections/gs_usb_driver/gs_usb.pri create mode 100644 connections/gs_usb_driver/gs_usb_definitions.h create mode 100644 connections/gs_usb_driver/gs_usb_functions.h create mode 100644 connections/gs_usb_driver/gs_usb_reader.cpp create mode 100644 connections/gs_usb_driver/gs_usb_reader.h diff --git a/README.md b/README.md index 901d4982..a2eb9cf2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ within the collin80 repos. It is now possible to use any Qt SerialBus driver (socketcan, Vector, PeakCAN, TinyCAN). +It's now also possible to connect to CANable, CAN Bus Debugger, or any other device using a variant +of the gs_usb protocol natively in Windows. These devices can be connected using socketcan on other +operating systems. + It should, however, be noted that use of a capture device is not required to make use of this program. It can load and save in several formats: diff --git a/SavvyCAN.pro b/SavvyCAN.pro index ceb0aaef..44b44edb 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -111,6 +111,7 @@ HEADERS += mainwindow.h \ can_structs.h \ canbridgewindow.h \ canframemodel.h \ + connections/gs_usb_driver/gs_usb_definitions.h \ connections/canlogserver.h \ connections/canserver.h \ connections/lawicel_serial.h \ @@ -248,6 +249,8 @@ win32-msvc* { win32-g++ { LIBS += libopengl32 + DEFINES += GS_USB_DRIVER_ENABLED + include($$PWD/connections/gs_usb_driver/gs_usb.pri) } unix { diff --git a/connections/canconconst.h b/connections/canconconst.h index 0f7ba9c0..d32d917a 100644 --- a/connections/canconconst.h +++ b/connections/canconconst.h @@ -23,6 +23,7 @@ namespace CANCon { LAWICEL, CANSERVER, CANLOGSERVER, + GSUSB, NONE }; } diff --git a/connections/canconfactory.cpp b/connections/canconfactory.cpp index 74234801..ac832620 100644 --- a/connections/canconfactory.cpp +++ b/connections/canconfactory.cpp @@ -7,6 +7,7 @@ #include "lawicel_serial.h" #include "canserver.h" #include "canlogserver.h" +#include "gs_usb_driver/gs_usb.h" using namespace CANCon; @@ -32,6 +33,18 @@ CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDri return new CANserver(pPortName); case CANLOGSERVER: return new CanLogServer(pPortName); +#ifdef GS_USB_DRIVER_ENABLED + case GSUSB: + { + GSUSBDevice* device = new GSUSBDevice(pPortName, pBusSpeed, pSamplePoint); + if (!device->isDeviceConnected()) { + delete device; + return nullptr; + } else { + return device; + } + } +#endif default: {} } diff --git a/connections/canconnectionmodel.cpp b/connections/canconnectionmodel.cpp index c014f558..e1f86595 100644 --- a/connections/canconnectionmodel.cpp +++ b/connections/canconnectionmodel.cpp @@ -89,6 +89,7 @@ QVariant CANConnectionModel::data(const QModelIndex &index, int role) const case CANCon::LAWICEL: return "LAWICEL"; case CANCon::CANSERVER: return "CANserver"; case CANCon::CANLOGSERVER: return "CanLogServer"; + case CANCon::GSUSB: return "gs_usb"; default: {} } else qDebug() << "Tried to show connection type but connection was nullptr"; diff --git a/connections/connectionwindow.cpp b/connections/connectionwindow.cpp index 29ed1880..2c133b75 100644 --- a/connections/connectionwindow.cpp +++ b/connections/connectionwindow.cpp @@ -2,6 +2,7 @@ #include #include +#include "gs_usb_driver/gs_usb.h" #include "connectionwindow.h" #include "mainwindow.h" #include "helpwindow.h" @@ -247,7 +248,6 @@ void ConnectionWindow::consoleEnableChanged(bool checked) { void ConnectionWindow::handleNewConn() { - NewConnectionDialog *thisDialog = new NewConnectionDialog(&remoteDeviceIPGVRET, &remoteDeviceKayak); CANCon::type newType; QString newPort; QString newDriver; @@ -258,6 +258,12 @@ void ConnectionWindow::handleNewConn() int newDataRate; CANConnection *conn; +#ifdef GS_USB_DRIVER_ENABLED + // Get GS USB devices before opening new connection window + GSUSBDevice::getConnectedDevices(remoteDeviceGSUSB); +#endif + + NewConnectionDialog *thisDialog = new NewConnectionDialog(&remoteDeviceIPGVRET, &remoteDeviceKayak, &remoteDeviceGSUSB); if (thisDialog->exec() == QDialog::Accepted) { newType = thisDialog->getConnectionType(); @@ -383,7 +389,25 @@ void ConnectionWindow::saveBusSettings() return; } - bus.setSpeed(ui->cbBusSpeed->currentText().toInt()); + if (conn_p->getType() == CANCon::type::GSUSB) { +#ifdef GS_USB_DRIVER_ENABLED + QList supportedTiming; + GSUSBDevice* device = (GSUSBDevice*) conn_p; + int currentSpeedIndex = ui->cbBusSpeed->currentIndex(); + if ((currentSpeedIndex >= 0) && device->isDeviceConnected()){ + candle_handle handle = device->getDeviceHandle(); + GSUSBDevice::getSupportedTimings(handle, supportedTiming); + if (supportedTiming.length() > currentSpeedIndex) { + GSUSB_timing_t timing = supportedTiming.at(currentSpeedIndex); + bus.setSpeed((int) timing.bitrate); + bus.setSamplePoint((int) timing.samplePoint); + } + } +#endif + } else { + bus.setSpeed(ui->cbBusSpeed->currentText().toInt()); + } + bus.setActive(ui->ckEnable->isChecked()); bus.setListenOnly(ui->ckListenOnly->isChecked()); bus.setCanFD(ui->canFDEnable->isChecked()); @@ -395,6 +419,7 @@ void ConnectionWindow::saveBusSettings() void ConnectionWindow::populateBusDetails(int offset) { int selIdx = ui->tableConnections->currentIndex().row(); + bool found = false; /* set parameters */ if (selIdx == -1) { @@ -431,18 +456,54 @@ void ConnectionWindow::populateBusDetails(int offset) ui->dataRate_label->setVisible(true); } - bool found = false; - for (int i = 0; i < ui->cbBusSpeed->count(); i++) - { - if (bus.getSpeed() == ui->cbBusSpeed->itemText(i).toInt()) - { - found = true; - ui->cbBusSpeed->setCurrentIndex(i); - break; + if (conn_p->getType() == CANCon::type::GSUSB) { + ui->ckEnable->setEnabled(false); + ui->cbBusSpeed->clear(); +#ifdef GS_USB_DRIVER_ENABLED + GSUSBDevice* device = (GSUSBDevice*) conn_p; + int selectionIndex = 0; + if (device->isDeviceConnected()) { + candle_handle handle = device->getDeviceHandle(); + QList supportedTiming; + GSUSBDevice::getSupportedTimings(handle, supportedTiming); + foreach (const GSUSB_timing_t timing, supportedTiming) { + ui->cbBusSpeed->addItem(GSUSBDevice::timingToString(timing)); + if ((bus.getSpeed() == (int) timing.bitrate) && (bus.getSamplePoint() == (int) timing.samplePoint)) { + found = true; + } else if (found == false) { + selectionIndex++; + } + } + } + if (found) { + ui->cbBusSpeed->setCurrentIndex(selectionIndex); + } +#endif + } else { + ui->ckEnable->setEnabled(true); + ui->cbBusSpeed->clear(); + + ui->cbBusSpeed->addItem("33333"); + ui->cbBusSpeed->addItem("50000"); + ui->cbBusSpeed->addItem("83333"); + ui->cbBusSpeed->addItem("100000"); + ui->cbBusSpeed->addItem("125000"); + ui->cbBusSpeed->addItem("250000"); + ui->cbBusSpeed->addItem("500000"); + ui->cbBusSpeed->addItem("1000000"); + + for (int i = 0; i < ui->cbBusSpeed->count(); i++) { + if (bus.getSpeed() == ui->cbBusSpeed->itemText(i).toInt()) { + found = true; + ui->cbBusSpeed->setCurrentIndex(i); + break; + } + } + if (!found) { + ui->cbBusSpeed->addItem(QString::number(bus.getSpeed())); } } - if (!found) ui->cbBusSpeed->addItem(QString::number(bus.getSpeed())); found = false; for (int i = 0; i < ui->cbDataRate->count(); i++) { diff --git a/connections/connectionwindow.h b/connections/connectionwindow.h index 8f629b83..39bcd198 100644 --- a/connections/connectionwindow.h +++ b/connections/connectionwindow.h @@ -13,6 +13,7 @@ #include #include "canconnectionmodel.h" #include "connections/canconnection.h" +#include "gs_usb_driver/gs_usb_definitions.h" class CANConnectionModel; @@ -64,6 +65,7 @@ private slots: QUdpSocket *rxBroadcastKayak; QVector remoteDeviceIPGVRET; QVector remoteDeviceKayak; + QVector remoteDeviceGSUSB; CANConnection* create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate, int pSamplePoint); void populateBusDetails(int offset); diff --git a/connections/gs_usb_driver/gs_usb.cpp b/connections/gs_usb_driver/gs_usb.cpp new file mode 100644 index 00000000..0531afbe --- /dev/null +++ b/connections/gs_usb_driver/gs_usb.cpp @@ -0,0 +1,467 @@ +/* +* gs_usb.cpp +* +* Ben Evans +* +* MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +/* +* NOTE REGARDING NAMING +* +* I'd like to change the name of the candle_dll repo to gs_usb_dll to be more generic as this driver +* should support all devices following the gs_usb driver protocol. However, I also don't want to take +* away from the original authors contribution to that code and so for now will keep the name. +* +* If development continues to add FD support to the driver etc. I'll make the name switch then. +* +* https://github.com/BennyEvans/candle_dll +*/ + +#include "gs_usb.h" +#include "gs_usb_definitions.h" +#include "gs_usb_functions.h" +#include "gs_usb_reader.h" +#include +#include +#include + +#define GS_USB_NUM_BUSES (1) +#define GS_USB_BUS_CHANNEL_ID (0) + + +GSUSBDevice::GSUSBDevice(QString portName, int busSpeed, int samplePoint) : + CANConnection(portName, "win_gs_usb_driver", CANCon::GSUSB, 0, busSpeed, samplePoint, false, 0, GS_USB_NUM_BUSES, 16000, true) +{ + candle_list_handle clist; + uint8_t num_interfaces; + candle_handle dev; + + sendDebug("GSUSBDevice()"); + + if (candle_list_scan(&clist)) { + if (candle_list_length(clist, &num_interfaces)) { + for (uint8_t i = 0; i < num_interfaces; i++) { + if (candle_dev_get(clist, i, &dev)) { + if (QString::compare(portName, QString::fromWCharArray((wchar_t *) candle_dev_get_path(dev))) == 0) { + sendDebug("GSUSBDevice found"); + this->deviceHandle = dev; + this->deviceConnected = true; + break; + } + } + } + } + candle_list_free(clist); + } + if (this->deviceConnected == false) { + sendDebug("GSUSBDevice not found"); + } + + getSupportedTimings(this->deviceHandle, this->supportedTimings); +} + +GSUSBDevice::~GSUSBDevice() +{ + stop(); + sendDebug("~GSUSBDevice()"); +} + +void GSUSBDevice::sendDebug(const QString debugText) +{ + qDebug() << debugText; + emit debugOutput(debugText); +} + +void GSUSBDevice::piStarted() +{ + uint32_t setupFlags = 0; + if (this->deviceConnected == true) { + + if (this->reader != NULL) { + this->reader->stopReading(); + } + + // Get capability + candle_capability_t capability; + if (!candle_channel_get_capabilities(this->deviceHandle, GS_USB_BUS_CHANNEL_ID, &capability)) { + sendDebug("GSUSBDevice failed to get capability"); + return; + } + + //Connect + if (!candle_dev_open(this->deviceHandle)) { + sendDebug("GSUSBDevice could not open"); + return; + } else { + sendDebug("GSUSBDevice device open"); + } + + if (mBusData[GS_USB_BUS_CHANNEL_ID].mConfigured == false) { + // Set default config for unset items + mBusData[GS_USB_BUS_CHANNEL_ID].mBus.setListenOnly(false); + mBusData[GS_USB_BUS_CHANNEL_ID].mBus.setActive(true); + mBusData[GS_USB_BUS_CHANNEL_ID].mConfigured = true; + } + + // Set the bit timing + if (!setBitTiming(mBusData[GS_USB_BUS_CHANNEL_ID].mBus.getSpeed(), mBusData[GS_USB_BUS_CHANNEL_ID].mBus.getSamplePoint())) { + sendDebug("GSUSBDevice could not set bus speed"); + return; + } else { + sendDebug("GSUSBDevice bus speed set to " + QString::number(mBusData[GS_USB_BUS_CHANNEL_ID].mBus.getSpeed()) + "bps @ " + + QString::number((mBusData[GS_USB_BUS_CHANNEL_ID].mBus.getSamplePoint() / 10.0f), 'f', 1) + "%"); + } + + // Create new reader + this->reader = new GSUSBReader(NULL, this->deviceHandle); + + // TODO: Maybe this could just be QueuedConnection? If speed up required, check this! + bool result = connect(this->reader, SIGNAL(newFrame(candle_frame_t)), this, SLOT(readFrame(candle_frame_t)), Qt::BlockingQueuedConnection); + Q_ASSERT(result); + + // TODO: In the future, add one shot support etc. + if (mBusData[GS_USB_BUS_CHANNEL_ID].mBus.isListenOnly() && (capability.feature & CANDLE_MODE_LISTEN_ONLY)) { + setupFlags |= CANDLE_MODE_LISTEN_ONLY; + sendDebug("GSUSBDevice listen only enabled"); + } + if (capability.feature & CANDLE_MODE_HW_TIMESTAMP) { + // Always enable HW timestamps if available + setupFlags |= CANDLE_MODE_HW_TIMESTAMP; + sendDebug("GSUSBDevice using HW timestamps"); + } else { + // Continue to connect, but frame timestamps are going to be all over the place + sendDebug("GSUSBDevice no HW timestamp support"); + } + + if (!candle_channel_start(this->deviceHandle, GS_USB_BUS_CHANNEL_ID, setupFlags)) { + sendDebug("GSUSBDevice could not start channel"); + return; + } else { + sendDebug("GSUSBDevice channel started"); + } + + // Start listening for messages + this->reader->startReading(); + + setStatus(CANCon::CONNECTED); + CANConStatus stats; + stats.conStatus = getStatus(); + stats.numHardwareBuses = 1; + emit status(stats); + } +} + +void GSUSBDevice::piSuspend(bool pSuspend) +{ + // Update capSuspended + setCapSuspended(pSuspend); + + // Flush queue if we are suspended + if (isCapSuspended()) { + getQueue().flush(); + } +} + +void GSUSBDevice::piStop() +{ + if (this->deviceConnected == true) { + if (this->reader != NULL) { + this->reader->stopReading(); + } + this->reader = NULL; + candle_channel_stop(this->deviceHandle, GS_USB_BUS_CHANNEL_ID); + candle_dev_close(this->deviceHandle); + + setStatus(CANCon::NOT_CONNECTED); + CANConStatus stats; + stats.conStatus = getStatus(); + stats.numHardwareBuses = mNumBuses; + emit status(stats); + } +} + +bool GSUSBDevice::piGetBusSettings(int pBusIdx, CANBus& pBus) +{ + return getBusConfig(pBusIdx, pBus); +} + +void GSUSBDevice::piSetBusSettings(int pBusIdx, CANBus bus) +{ + if ((pBusIdx >= 0) && (pBusIdx <= getNumBuses())) { + setBusConfig(pBusIdx, bus); + // stop and re-enable the bus to apply the settings + this->piStop(); + this->piStarted(); + } +} + +bool GSUSBDevice::piSendFrame(const CANFrame& frame) +{ + candle_frame_t sendFrame; + memset(&sendFrame, 0, sizeof(sendFrame)); + + if (frame.hasExtendedFrameFormat()) { + sendFrame.can_id |= CANDLE_ID_EXTENDED; + sendFrame.can_id |= frame.frameId() & 0x1FFFFFFF; + } else { + sendFrame.can_id |= frame.frameId() & 0x7FF; + } + + switch (frame.frameType()) { + case CANFrame::RemoteRequestFrame: + sendFrame.can_id |= CANDLE_ID_RTR; + break; + case CANFrame::DataFrame: + // Do nothing + break; + default: + return false; + } + + QByteArray payload = frame.payload(); + if (payload.length() > 8) { + return false; + } + sendFrame.can_dlc = payload.length(); + for (int i = 0; i < payload.length(); i++) { + sendFrame.data[i] = payload.at(i); + } + + // Send the frame + if (candle_frame_send(this->deviceHandle, GS_USB_BUS_CHANNEL_ID, &sendFrame, true, 1000)) { + return true; + } + return false; +} + +bool GSUSBDevice::isDeviceConnected() +{ + return this->deviceConnected; +} + +candle_handle GSUSBDevice::getDeviceHandle() +{ + return this->deviceHandle; +} + +bool GSUSBDevice::setBitTiming(unsigned int bitrate, unsigned int samplePoint) +{ + if (this->deviceConnected) { + bool timingFound = false; + candle_bittiming_t bitTiming; + foreach (const GSUSB_timing_t timing, this->supportedTimings) { + if ((timing.bitrate == bitrate) && (timing.samplePoint == samplePoint)) { + bitTiming = timingToCandleBitTiming(timing); + timingFound = true; + break; + } + } + if (timingFound) { + return candle_channel_set_timing(this->deviceHandle, GS_USB_BUS_CHANNEL_ID, &bitTiming); + } + } + return false; +} + +void GSUSBDevice::readFrame(const candle_frame_t frame) +{ + if (!isCapSuspended()) { + CANFrame* queuedFrame = getQueue().get(); + if(queuedFrame) { + queuedFrame->bus = 0; + queuedFrame->isReceived = true; + + // Don't populate the seconds field as only us is used + CANFrame::TimeStamp timestamp = QCanBusFrame::TimeStamp(0, frame.timestamp_us); + queuedFrame->setTimeStamp(timestamp); + + if (frame.can_id & CANDLE_ID_EXTENDED) { + // Extended ID + queuedFrame->setExtendedFrameFormat(true); + queuedFrame->setFrameId(frame.can_id & 0x1FFFFFFF); + } else { + // Standard ID + queuedFrame->setExtendedFrameFormat(false); + queuedFrame->setFrameId(frame.can_id & 0x7FF); + } + + if (frame.can_id & CANDLE_ID_ERROR) { + queuedFrame->setFrameType(QCanBusFrame::FrameType::ErrorFrame); + } else if (frame.can_id & CANDLE_ID_RTR) { + queuedFrame->setFrameType(QCanBusFrame::FrameType::RemoteRequestFrame); + } else{ + queuedFrame->setFrameType(QCanBusFrame::FrameType::DataFrame); + } + + int length = frame.can_dlc; + if (length > 8) { + length = 8; + } + QByteArray data = QByteArray(reinterpret_cast(frame.data), length); + queuedFrame->setPayload(data); + + checkTargettedFrame(*queuedFrame); + getQueue().queue(); + } else { + sendDebug("ERROR: GSUSBDevice can't get queue frame"); + } + } +} + + +// ######Static Functions ###### + +void GSUSBDevice::getConnectedDevices(QVector &remoteDeviceGSUSB) +{ + candle_list_handle clist; + uint8_t num_interfaces; + candle_handle dev; + + // Get GS USB devices + remoteDeviceGSUSB.clear(); + if (candle_list_scan(&clist)) { + if (candle_list_length(clist, &num_interfaces)) { + for (uint8_t i=0; i &timings) +{ + // NOTE: In both candlelight and can bus debugger fw, programmed tseg1 is prop_seg + seg1 and thus + // to simplify things below, prop_seg is always set to 0 as it's incorporated into tseg1 + QList allTimings = { + // Devices running candleLight_fw with 48Mhz clock - https://github.com/candle-usb/candleLight_fw + {48000000, 20000, 625, 300, 0, 4, 3, 3}, + {48000000, 50000, 625, 120, 0, 4, 3, 3}, + {48000000, 100000, 625, 60, 0, 4, 3, 3}, + {48000000, 150000, 625, 40, 0, 4, 3, 3}, + {48000000, 200000, 625, 30, 0, 4, 3, 3}, + {48000000, 250000, 625, 24, 0, 4, 3, 3}, + {48000000, 300000, 625, 20, 0, 4, 3, 3}, + {48000000, 500000, 625, 12, 0, 4, 3, 3}, + {48000000, 800000, 625, 6, 0, 5, 4, 4}, + {48000000, 1000000, 625, 6, 0, 4, 3, 3}, + + {48000000, 20000, 750, 120, 0, 14, 5, 4}, + {48000000, 50000, 750, 48, 0, 14, 5, 4}, + {48000000, 100000, 750, 24, 0, 14, 5, 4}, + {48000000, 150000, 750, 16, 0, 14, 5, 4}, + {48000000, 200000, 750, 12, 0, 14, 5, 4}, + {48000000, 250000, 750, 12, 0, 11, 4, 4}, + {48000000, 300000, 750, 8, 0, 14, 5, 4}, + {48000000, 500000, 750, 6, 0, 11, 4, 4}, + {48000000, 800000, 750, 3, 0, 14, 5, 4}, + {48000000, 1000000, 750, 3, 0, 11, 4, 4}, + + {48000000, 20000, 875, 150, 0, 13, 2, 2}, + {48000000, 50000, 875, 60, 0, 13, 2, 2}, + {48000000, 100000, 875, 30, 0, 13, 2, 2}, + {48000000, 150000, 875, 20, 0, 13, 2, 2}, + {48000000, 200000, 875, 15, 0, 13, 2, 2}, + {48000000, 250000, 875, 12, 0, 13, 2, 2}, + {48000000, 300000, 875, 10, 0, 13, 2, 2}, + {48000000, 500000, 875, 6, 0, 13, 2, 2}, + {48000000, 800000, 875, 4, 0, 12, 2, 2}, + {48000000, 1000000, 875, 3, 0, 13, 2, 2}, + + // CAN Bus Debugger device using 80Mhz clock - https://www.canbusdebugger.com + {80000000, 20000, 625, 100, 0, 24, 15, 15}, + {80000000, 50000, 625, 40, 0, 24, 15, 15}, + {80000000, 100000, 625, 20, 0, 24, 15, 15}, + {80000000, 150000, 625, 13, 0, 25, 15, 15}, + {80000000, 200000, 625, 10, 0, 24, 15, 15}, + {80000000, 250000, 625, 8, 0, 24, 15, 15}, + {80000000, 300000, 625, 7, 0, 23, 14, 14}, + {80000000, 500000, 625, 4, 0, 24, 15, 15}, + {80000000, 800000, 625, 4, 0, 15, 9, 9}, + {80000000, 1000000, 625, 2, 0, 24, 15, 15}, + + {80000000, 20000, 750, 100, 0, 29, 10, 10}, + {80000000, 50000, 750, 25, 0, 47, 16, 16}, + {80000000, 100000, 750, 20, 0, 29, 10, 10}, + {80000000, 150000, 750, 13, 0, 30, 10, 10}, + {80000000, 200000, 750, 10, 0, 29, 10, 10}, + {80000000, 250000, 750, 5, 0, 47, 16, 16}, + {80000000, 300000, 750, 7, 0, 27, 10, 10}, + {80000000, 500000, 750, 4, 0, 29, 10, 10}, + {80000000, 800000, 750, 5, 0, 14, 5, 5}, + {80000000, 1000000, 750, 2, 0, 29, 10, 10}, + + {80000000, 20000, 875, 100, 0, 34, 5, 5}, + {80000000, 50000, 875, 25, 0, 55, 8, 8}, + {80000000, 100000, 875, 20, 0, 34, 5, 5}, + {80000000, 150000, 875, 13, 0, 35, 5, 5}, + {80000000, 200000, 875, 10, 0, 34, 5, 5}, + {80000000, 250000, 875, 5, 0, 55, 8, 8}, + {80000000, 300000, 875, 7, 0, 32, 5, 5}, + {80000000, 500000, 875, 4, 0, 34, 5, 5}, + {80000000, 800000, 875, 2, 0, 43, 6, 6}, + {80000000, 1000000, 875, 2, 0, 34, 5, 5} + }; + + candle_capability_t capability; + if (candle_channel_get_capabilities(handle, GS_USB_BUS_CHANNEL_ID, &capability)) { + foreach (const GSUSB_timing_t timing, allTimings) { + candle_bittiming_t bitTiming = timingToCandleBitTiming(timing); + if ((timing.deviceClock == capability.device_clock) && + ((bitTiming.phase_seg1 >= capability.tseg1_min) && (bitTiming.phase_seg1 <= capability.tseg1_max)) && + ((bitTiming.phase_seg2 >= capability.tseg2_min) && (bitTiming.phase_seg2 <= capability.tseg2_max))) + { + timings.append(timing); + } + } + } +} + +candle_bittiming_t GSUSBDevice::timingToCandleBitTiming(const GSUSB_timing_t &timing) +{ + candle_bittiming_t timingOut; + timingOut.brp = timing.prescaler; + timingOut.prop_seg = timing.propSeg; + timingOut.phase_seg1 = timing.tseg1; + timingOut.phase_seg2 = timing.tseg2; + timingOut.sjw = timing.sjw; + return timingOut; +} + +QString GSUSBDevice::handleToDeviceIDString(candle_handle handle) +{ + wchar_t* path = (wchar_t *) candle_dev_get_path(handle); + if (path != NULL) { + return QString::fromWCharArray((wchar_t *) candle_dev_get_path(handle)); + } + return QString(); +} + +QString GSUSBDevice::timingToString(const GSUSB_timing_t &timing) +{ + QString str; + QTextStream(&str) << QString::number(timing.bitrate) << " (SP: " << QString::number((timing.samplePoint / 10.0f), 'f', 1) << "%)"; + return str; +} diff --git a/connections/gs_usb_driver/gs_usb.h b/connections/gs_usb_driver/gs_usb.h new file mode 100644 index 00000000..9fe20b6b --- /dev/null +++ b/connections/gs_usb_driver/gs_usb.h @@ -0,0 +1,87 @@ +/* +* gs_usb.h +* +* Ben Evans +* +* MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef GSUSB_H +#define GSUSB_H + +#include "gs_usb_definitions.h" +#include "gs_usb_reader.h" +#include "connections/canconnection.h" + +typedef struct _GSUSB_timing_t_s +{ + unsigned int deviceClock; + unsigned int bitrate; + unsigned int samplePoint; + unsigned int prescaler; + unsigned int propSeg; + unsigned int tseg1; + unsigned int tseg2; + unsigned int sjw; +} GSUSB_timing_t; + + +class GSUSBDevice : public CANConnection +{ + Q_OBJECT + +public: + GSUSBDevice(QString portName, int busSpeed, int samplePoint); + virtual ~GSUSBDevice(); + bool isDeviceConnected(); + candle_handle getDeviceHandle(); + + static void getConnectedDevices(QVector &remoteDeviceGSUSB); + static void getSupportedTimings(candle_handle handle, QList &timings); + + static candle_bittiming_t timingToCandleBitTiming(const GSUSB_timing_t &timing); + static QString handleToDeviceIDString(candle_handle handle); + static QString timingToString(const GSUSB_timing_t &timing); + +protected: + virtual void piStarted(); + virtual void piStop(); + virtual void piSetBusSettings(int pBusIdx, CANBus pBus); + virtual bool piGetBusSettings(int pBusIdx, CANBus& pBus); + virtual void piSuspend(bool pSuspend); + virtual bool piSendFrame(const CANFrame&); + +private slots: + void readFrame(const candle_frame_t frame); + +private: + bool setBitTiming(unsigned int bitrate, unsigned int samplePoint); + void sendDebug(const QString debugText); + +protected: + bool deviceConnected = false; + candle_handle deviceHandle; + GSUSBReader* reader = NULL; + QList supportedTimings; + +}; + +#endif // GSUSB_H diff --git a/connections/gs_usb_driver/gs_usb.pri b/connections/gs_usb_driver/gs_usb.pri new file mode 100644 index 00000000..6ffe36ce --- /dev/null +++ b/connections/gs_usb_driver/gs_usb.pri @@ -0,0 +1,11 @@ +SOURCES += \ + $$PWD/gs_usb.cpp \ + $$PWD/gs_usb_reader.cpp + +HEADERS += \ + $$PWD/gs_usb_definitions.h \ + $$PWD/gs_usb_functions.h \ + $$PWD/gs_usb.h \ + $$PWD/gs_usb_reader.h + +LIBS += -L$$PWD\..\..\libs\candle_api -llibcandle_api.dll diff --git a/connections/gs_usb_driver/gs_usb_definitions.h b/connections/gs_usb_driver/gs_usb_definitions.h new file mode 100644 index 00000000..06b67600 --- /dev/null +++ b/connections/gs_usb_driver/gs_usb_definitions.h @@ -0,0 +1,155 @@ +/* +* gs_usb_definitions.h +* +* Ben Evans +* +* MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +/* +* NOTE REGARDING NAMING +* +* I'd like to change the name of the candle_dll repo to gs_usb_dll to be more generic as this driver +* should support all devices following the gs_usb driver protocol. However, I also don't want to take +* away from the original authors contribution to that code and so for now will keep the name. +* +* If development continues to add FD support etc. to the driver code, I'll make the name switch then +* and adjust all these function names/defines. +* +* https://github.com/BennyEvans/candle_dll +*/ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CANDLE_ID_EXTENDED (0x80000000U) +#define CANDLE_ID_RTR (0x40000000U) +#define CANDLE_ID_ERROR (0x20000000U) + +typedef enum { + CANDLE_MODE_NORMAL = 0x00, + CANDLE_MODE_LISTEN_ONLY = 0x01, + CANDLE_MODE_LOOP_BACK = 0x02, + CANDLE_MODE_TRIPLE_SAMPLE = 0x04, + CANDLE_MODE_ONE_SHOT = 0x08, + CANDLE_MODE_HW_TIMESTAMP = 0x10, + CANDLE_MODE_PAD_PKTS_TO_MAX_PKT_SIZE = 0x80 +} candle_mode_t; + +typedef enum { + CANDLE_ERR_OK = 0, + CANDLE_ERR_CREATE_FILE = 1, + CANDLE_ERR_WINUSB_INITIALIZE = 2, + CANDLE_ERR_QUERY_INTERFACE = 3, + CANDLE_ERR_QUERY_PIPE = 4, + CANDLE_ERR_PARSE_IF_DESCR = 5, + CANDLE_ERR_SET_HOST_FORMAT = 6, + CANDLE_ERR_GET_DEVICE_INFO = 7, + CANDLE_ERR_GET_BITTIMING_CONST = 8, + CANDLE_ERR_PREPARE_READ = 9, + CANDLE_ERR_SET_DEVICE_MODE = 10, + CANDLE_ERR_SET_BITTIMING = 11, + CANDLE_ERR_BITRATE_FCLK = 12, + CANDLE_ERR_BITRATE_UNSUPPORTED = 13, + CANDLE_ERR_SEND_FRAME = 14, + CANDLE_ERR_READ_TIMEOUT = 15, + CANDLE_ERR_READ_WAIT = 16, + CANDLE_ERR_READ_RESULT = 17, + CANDLE_ERR_READ_SIZE = 18, + CANDLE_ERR_SETUPDI_IF_DETAILS = 19, + CANDLE_ERR_SETUPDI_IF_DETAILS2 = 20, + CANDLE_ERR_MALLOC = 21, + CANDLE_ERR_PATH_LEN = 22, + CANDLE_ERR_CLSID = 23, + CANDLE_ERR_GET_DEVICES = 24, + CANDLE_ERR_SETUPDI_IF_ENUM = 25, + CANDLE_ERR_SET_TIMESTAMP_MODE = 26, + CANDLE_ERR_DEV_OUT_OF_RANGE = 27, + CANDLE_ERR_GET_TIMESTAMP = 28, + CANDLE_ERR_SET_PIPE_RAW_IO = 29, + CANDLE_ERR_DEVICE_FEATURE_UNAVAILABLE = 30, + CANDLE_ERR_WRITE_WAIT = 31, + CANDLE_ERR_WRITE_TIMEOUT = 32, + CANDLE_ERR_WRITE_RESULT = 33, + CANDLE_ERR_WRITE_SIZE = 34, + CANDLE_ERR_BUFFER_LEN = 35 +} candle_err_t; + +typedef enum { + CANDLE_DEVSTATE_AVAIL, + CANDLE_DEVSTATE_INUSE +} candle_devstate_t; + +typedef enum { + CANDLE_FRAMETYPE_UNKNOWN, + CANDLE_FRAMETYPE_RECEIVE, + CANDLE_FRAMETYPE_ECHO, + CANDLE_FRAMETYPE_ERROR, + CANDLE_FRAMETYPE_TIMESTAMP_OVFL +} candle_frametype_t; + +// Pack structures +#pragma pack(push,1) +typedef struct { + uint32_t echo_id; + uint32_t can_id; + uint8_t can_dlc; + uint8_t channel; + uint8_t flags; + uint8_t _reserved; + uint8_t data[8]; + uint32_t timestamp_us; +} candle_frame_t; + +typedef struct { + uint32_t feature; + uint32_t device_clock; + uint32_t tseg1_min; + uint32_t tseg1_max; + uint32_t tseg2_min; + uint32_t tseg2_max; + uint32_t sjw_max; + uint32_t brp_min; + uint32_t brp_max; + uint32_t brp_inc; +} candle_capability_t; + +typedef struct { + uint32_t prop_seg; + uint32_t phase_seg1; + uint32_t phase_seg2; + uint32_t sjw; + uint32_t brp; +} candle_bittiming_t; +#pragma pack(pop) + +typedef void* candle_list_handle; +typedef void* candle_handle; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/connections/gs_usb_driver/gs_usb_functions.h b/connections/gs_usb_driver/gs_usb_functions.h new file mode 100644 index 00000000..70059fed --- /dev/null +++ b/connections/gs_usb_driver/gs_usb_functions.h @@ -0,0 +1,79 @@ +/* +* gs_usb_functions.h +* +* Ben Evans +* +* MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +/* +* NOTE REGARDING NAMING +* +* I'd like to change the name of the candle_dll repo to gs_usb_dll to be more generic as this driver +* should support all devices following the gs_usb driver protocol. However, I also don't want to take +* away from the original authors contribution to that code and so for now will keep the name. +* +* If development continues to add FD support etc. to the driver code, I'll make the name switch then +* and adjust all these function names/defines. +* +* https://github.com/BennyEvans/candle_dll +*/ + +#pragma once + +#include "gs_usb_definitions.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool __stdcall candle_list_scan(candle_list_handle *list); +bool __stdcall candle_list_free(candle_list_handle list); +bool __stdcall candle_list_length(candle_list_handle list, uint8_t *len); +bool __stdcall candle_dev_get(candle_list_handle list, uint8_t dev_num, candle_handle *hdev); +bool __stdcall candle_dev_get_state(candle_handle hdev, candle_devstate_t *state); +wchar_t* __stdcall candle_dev_get_path(candle_handle hdev); +bool __stdcall candle_dev_open(candle_handle hdev); +bool __stdcall candle_dev_get_timestamp_us(candle_handle hdev, uint32_t *timestamp_us); +bool __stdcall candle_dev_close(candle_handle hdev); +bool __stdcall candle_dev_free(candle_handle hdev); +bool __stdcall candle_channel_count(candle_handle hdev, uint8_t *num_channels); +bool __stdcall candle_channel_get_capabilities(candle_handle hdev, uint8_t ch, candle_capability_t *cap); +bool __stdcall candle_channel_set_timing(candle_handle hdev, uint8_t ch, candle_bittiming_t *data); +bool __stdcall candle_channel_set_bitrate(candle_handle hdev, uint8_t ch, uint32_t bitrate); +bool __stdcall candle_channel_start(candle_handle hdev, uint8_t ch, uint32_t flags); +bool __stdcall candle_channel_stop(candle_handle hdev, uint8_t ch); +bool __stdcall candle_frame_send(candle_handle hdev, uint8_t ch, candle_frame_t *frame,bool wait_send, uint32_t timeout_ms); +bool __stdcall candle_frame_read(candle_handle hdev, candle_frame_t *frame, uint32_t timeout_ms); +candle_frametype_t __stdcall candle_frame_type(candle_frame_t *frame); +uint32_t __stdcall candle_frame_id(candle_frame_t *frame); +bool __stdcall candle_frame_is_extended_id(candle_frame_t *frame); +bool __stdcall candle_frame_is_rtr(candle_frame_t *frame); +uint8_t __stdcall candle_frame_dlc(candle_frame_t *frame); +uint8_t* __stdcall candle_frame_data(candle_frame_t *frame); +uint32_t __stdcall candle_frame_timestamp_us(candle_frame_t *frame); +candle_err_t __stdcall candle_dev_last_error(candle_handle hdev); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/connections/gs_usb_driver/gs_usb_reader.cpp b/connections/gs_usb_driver/gs_usb_reader.cpp new file mode 100644 index 00000000..e321716d --- /dev/null +++ b/connections/gs_usb_driver/gs_usb_reader.cpp @@ -0,0 +1,76 @@ +/* +* gs_usb_reader.cpp +* +* Ben Evans +* +* MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include "gs_usb_reader.h" +#include "gs_usb_definitions.h" +#include "gs_usb_functions.h" +#include +#include + +#define GS_USB_READER_BLOCK_TIME_MS (100U) + + +GSUSBReader::GSUSBReader(QObject *parent, candle_handle deviceHandle) : QObject(parent), deviceHandle(deviceHandle) +{ + this->thread = new QThread(); +} + +GSUSBReader::~GSUSBReader() +{ + stopReading(); + if (this->thread != NULL) { + delete this->thread; + } +} + +void GSUSBReader::run() +{ + // NOTE: In the future, maybe we could add a list here to reduce the number of signals being fired + candle_frame_t frame; + while (!QThread::currentThread()->isInterruptionRequested()) { + if (candle_frame_read(this->deviceHandle, &frame, GS_USB_READER_BLOCK_TIME_MS)) { + if (candle_frame_type(&frame) == CANDLE_FRAMETYPE_RECEIVE) { + emit newFrame(frame); + } + } + } + this->thread->quit(); +} + +void GSUSBReader::stopReading() +{ + if ((this->thread != NULL) && (this->thread->isRunning())) { + this->thread->requestInterruption(); + this->thread->wait(); + } +} + +void GSUSBReader::startReading() +{ + moveToThread(this->thread); + connect(this->thread, SIGNAL(started()), this, SLOT(run())); + this->thread->start(QThread::HighPriority); +} diff --git a/connections/gs_usb_driver/gs_usb_reader.h b/connections/gs_usb_driver/gs_usb_reader.h new file mode 100644 index 00000000..ef0616c5 --- /dev/null +++ b/connections/gs_usb_driver/gs_usb_reader.h @@ -0,0 +1,53 @@ +/* +* gs_usb_reader.h +* +* Ben Evans +* +* MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#pragma once + +#include "gs_usb_definitions.h" +#include +#include + +class GSUSBReader : public QObject +{ + Q_OBJECT + +public: + GSUSBReader(QObject *parent, candle_handle deviceHandle); + ~GSUSBReader(); + + void startReading(); + void stopReading(); + +signals: + void newFrame(const candle_frame_t frame); + +protected slots: + void run(); + +private: + QThread* thread; + candle_handle deviceHandle; +}; diff --git a/connections/newconnectiondialog.cpp b/connections/newconnectiondialog.cpp index 85163e29..19fe8390 100644 --- a/connections/newconnectiondialog.cpp +++ b/connections/newconnectiondialog.cpp @@ -1,12 +1,15 @@ #include #include "newconnectiondialog.h" #include "ui_newconnectiondialog.h" +#include "gs_usb_driver/gs_usb_definitions.h" +#include "gs_usb_driver/gs_usb.h" -NewConnectionDialog::NewConnectionDialog(QVector* gvretips, QVector* kayakhosts, QWidget *parent) : +NewConnectionDialog::NewConnectionDialog(QVector* gvretips, QVector* kayakhosts, QVector* gsusbIDs, QWidget *parent) : QDialog(parent), ui(new Ui::NewConnectionDialog), remoteDeviceIPGVRET(gvretips), - remoteBusKayak(kayakhosts) + remoteBusKayak(kayakhosts), + remoteDeviceGSUSB(gsusbIDs) { ui->setupUi(this); if (isSerialBusAvailable()) @@ -30,8 +33,14 @@ NewConnectionDialog::NewConnectionDialog(QVector* gvretips, QVectorrbLawicel, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->rbCANserver, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->rbCanlogserver, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); +#ifdef GS_USB_DRIVER_ENABLED + connect(ui->rbGSUSB, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); +#else + ui->rbGSUSB->setVisible(false); +#endif connect(ui->cbDeviceType, QOverload::of(&QComboBox::currentIndexChanged), this, &NewConnectionDialog::handleDeviceTypeChanged); + connect(ui->cbPort, QOverload::of(&QComboBox::currentIndexChanged), this, &NewConnectionDialog::handlePortChanged); connect(ui->btnOK, &QPushButton::clicked, this, &NewConnectionDialog::handleCreateButton); ui->lblDeviceType->setHidden(true); @@ -70,6 +79,7 @@ void NewConnectionDialog::handleConnTypeChanged() if (ui->rbMQTT->isChecked()) selectMQTT(); if (ui->rbCANserver->isChecked()) selectCANserver(); if (ui->rbCanlogserver->isChecked()) selectCANlogserver(); + if (ui->rbGSUSB->isChecked()) selectGSUSB(); } void NewConnectionDialog::handleDeviceTypeChanged() @@ -82,10 +92,30 @@ void NewConnectionDialog::handleDeviceTypeChanged() ui->cbPort->addItem(canDevices[i].name()); } +void NewConnectionDialog::handlePortChanged() +{ +#ifdef GS_USB_DRIVER_ENABLED + if (ui->rbGSUSB->isChecked()) { + // GS USB is checked - attempt to populate bitrate based on device capability + ui->cbCANSpeed->clear(); + int currentIndex = ui->cbPort->currentIndex(); + if ((currentIndex >= 0) && (currentIndex < remoteDeviceGSUSB->length())) { + QList supportedTiming; + candle_handle handle = remoteDeviceGSUSB->at(currentIndex); + GSUSBDevice::getSupportedTimings(handle, supportedTiming); + foreach (const GSUSB_timing_t timing, supportedTiming) { + ui->cbCANSpeed->addItem(GSUSBDevice::timingToString(timing)); + } + } + } +#endif +} + void NewConnectionDialog::selectLawicel() { ui->lPort->setText("Serial Port:"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); @@ -103,18 +133,17 @@ void NewConnectionDialog::selectLawicel() for (int i = 0; i < ports.count(); i++) ui->cbPort->addItem(ports[i].portName()); - if (ui->cbCANSpeed->count() == 0) - { - ui->cbCANSpeed->addItem("10000"); - ui->cbCANSpeed->addItem("20000"); - ui->cbCANSpeed->addItem("50000"); - ui->cbCANSpeed->addItem("83333"); - ui->cbCANSpeed->addItem("100000"); - ui->cbCANSpeed->addItem("125000"); - ui->cbCANSpeed->addItem("250000"); - ui->cbCANSpeed->addItem("500000"); - ui->cbCANSpeed->addItem("1000000"); - } + ui->cbCANSpeed->clear(); + ui->cbCANSpeed->addItem("10000"); + ui->cbCANSpeed->addItem("20000"); + ui->cbCANSpeed->addItem("50000"); + ui->cbCANSpeed->addItem("83333"); + ui->cbCANSpeed->addItem("100000"); + ui->cbCANSpeed->addItem("125000"); + ui->cbCANSpeed->addItem("250000"); + ui->cbCANSpeed->addItem("500000"); + ui->cbCANSpeed->addItem("1000000"); + if (ui->cbDataRate->count() == 0) { ui->cbDataRate->addItem("1000000"); @@ -139,6 +168,7 @@ void NewConnectionDialog::selectSerial() { ui->lPort->setText("Serial Port:"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); ui->cbCANSpeed->setHidden(true); @@ -159,6 +189,7 @@ void NewConnectionDialog::selectSerial() void NewConnectionDialog::selectSocketCan() { ui->lPort->setText("Port:"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(false); ui->cbDeviceType->setHidden(false); ui->cbCANSpeed->setHidden(true); @@ -181,6 +212,7 @@ void NewConnectionDialog::selectRemote() { ui->lPort->setText("IP Address:"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); ui->cbCANSpeed->setHidden(true); @@ -202,6 +234,7 @@ void NewConnectionDialog::selectKayak() { ui->lPort->setText("Available Bus(ses):"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); ui->cbCANSpeed->setHidden(true); @@ -219,10 +252,34 @@ void NewConnectionDialog::selectKayak() } } +void NewConnectionDialog::selectGSUSB() +{ + ui->lPort->setText("Port:"); + ui->cbPort->setEditable(false); + ui->lblDeviceType->setHidden(true); + ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(false); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(false); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); + + ui->cbCANSpeed->clear(); + ui->cbPort->clear(); +#ifdef GS_USB_DRIVER_ENABLED + foreach(candle_handle handle, *remoteDeviceGSUSB) { + ui->cbPort->addItem(GSUSBDevice::handleToDeviceIDString(handle)); + } +#endif +} + void NewConnectionDialog::selectMQTT() { ui->lPort->setText("Topic Name:"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); ui->cbCANSpeed->setHidden(true); @@ -240,6 +297,7 @@ void NewConnectionDialog::selectCANserver() { ui->lPort->setText("CANserver IP Address:"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); ui->cbCANSpeed->setHidden(true); @@ -257,6 +315,7 @@ void NewConnectionDialog::selectCANlogserver() { ui->lPort->setText("CANlogserver IP Address:"); + ui->cbPort->setEditable(true); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); ui->cbCANSpeed->setHidden(true); @@ -294,11 +353,14 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt ui->rbLawicel->setChecked(true); break; case CANCon::CANSERVER: - ui->rbCANserver->setChecked(true); - break; + ui->rbCANserver->setChecked(true); + break; case CANCon::CANLOGSERVER: - ui->rbCanlogserver->setChecked(true); - break; + ui->rbCanlogserver->setChecked(true); + break; + case CANCon::GSUSB: + ui->rbGSUSB->setChecked(true); + break; default: {} } @@ -307,6 +369,9 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt switch(pType) { + case CANCon::GSUSB: + // Note that this function isn't currently called + // For now, just fall through to default functionality case CANCon::GVRET_SERIAL: case CANCon::LAWICEL: { @@ -365,8 +430,8 @@ QString NewConnectionDialog::getPortName() return ui->cbPort->currentText(); case CANCon::CANSERVER: case CANCon::CANLOGSERVER: + case CANCon::GSUSB: return ui->cbPort->currentText(); - default: qDebug() << "getPortName: can't get port"; } @@ -394,11 +459,46 @@ int NewConnectionDialog::getSerialSpeed() int NewConnectionDialog::getBusSpeed() { - if (getConnectionType() == CANCon::LAWICEL) - { + if (getConnectionType() == CANCon::LAWICEL) { return ui->cbCANSpeed->currentText().toInt(); + } else if (getConnectionType() == CANCon::GSUSB) { +#ifdef GS_USB_DRIVER_ENABLED + QList supportedTiming; + int currentDeviceIndex = ui->cbPort->currentIndex(); + int currentSpeedIndex = ui->cbCANSpeed->currentIndex(); + if ((currentDeviceIndex >= 0) && (currentSpeedIndex >= 0)) { + candle_handle handle = remoteDeviceGSUSB->at(currentDeviceIndex); + GSUSBDevice::getSupportedTimings(handle, supportedTiming); + if (supportedTiming.length() > currentSpeedIndex) { + return supportedTiming.at(currentSpeedIndex).bitrate; + } + } +#endif + return 0; + } else { + return 0; + } +} + +int NewConnectionDialog::getSamplePoint() +{ + if (getConnectionType() == CANCon::GSUSB) { +#ifdef GS_USB_DRIVER_ENABLED + QList supportedTiming; + int currentDeviceIndex = ui->cbPort->currentIndex(); + int currentSpeedIndex = ui->cbCANSpeed->currentIndex(); + if ((currentDeviceIndex >= 0) && (currentSpeedIndex >= 0)) { + candle_handle handle = remoteDeviceGSUSB->at(currentDeviceIndex); + GSUSBDevice::getSupportedTimings(handle, supportedTiming); + if (supportedTiming.length() > currentSpeedIndex) { + return supportedTiming.at(currentSpeedIndex).samplePoint; + } + } +#endif + return 0; + } else { + return 875; } - else return 0; } CANCon::type NewConnectionDialog::getConnectionType() @@ -411,6 +511,7 @@ CANCon::type NewConnectionDialog::getConnectionType() if (ui->rbLawicel->isChecked()) return CANCon::LAWICEL; if (ui->rbCANserver->isChecked()) return CANCon::CANSERVER; if (ui->rbCanlogserver->isChecked()) return CANCon::CANLOGSERVER; + if (ui->rbGSUSB->isChecked()) return CANCon::GSUSB; qDebug() << "getConnectionType: error"; return CANCon::NONE; diff --git a/connections/newconnectiondialog.h b/connections/newconnectiondialog.h index 66512af1..ac6518b0 100644 --- a/connections/newconnectiondialog.h +++ b/connections/newconnectiondialog.h @@ -8,6 +8,7 @@ #include #include "canconnectionmodel.h" #include "connections/canconnection.h" +#include "gs_usb_driver/gs_usb_definitions.h" namespace Ui { class NewConnectionDialog; @@ -18,7 +19,7 @@ class NewConnectionDialog : public QDialog Q_OBJECT public: - explicit NewConnectionDialog(QVector* gvretips, QVector* kayakips, QWidget *parent = nullptr); + explicit NewConnectionDialog(QVector* gvretips, QVector* kayakips, QVector* gsusbIDs, QWidget *parent = nullptr); ~NewConnectionDialog(); CANCon::type getConnectionType(); @@ -33,6 +34,7 @@ class NewConnectionDialog : public QDialog public slots: void handleConnTypeChanged(); void handleDeviceTypeChanged(); + void handlePortChanged(); void handleCreateButton(); private: @@ -41,6 +43,7 @@ public slots: QList canDevices; QVector* remoteDeviceIPGVRET; QVector* remoteBusKayak; + QVector* remoteDeviceGSUSB; void selectSerial(); void selectKvaser(); @@ -51,6 +54,7 @@ public slots: void selectLawicel(); void selectCANserver(); void selectCANlogserver(); + void selectGSUSB(); bool isSerialBusAvailable(); void setPortName(CANCon::type pType, QString pPortName, QString pDriver); }; diff --git a/ui/newconnectiondialog.ui b/ui/newconnectiondialog.ui index e4595833..b934319d 100644 --- a/ui/newconnectiondialog.ui +++ b/ui/newconnectiondialog.ui @@ -7,7 +7,7 @@ 0 0 426 - 540 + 749 @@ -23,10 +23,20 @@ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + - socketcand (SocketCAN over Ethernet) + MQTT Connection + + + + + + + Serial Connection (GVRET) + + + true @@ -40,20 +50,10 @@ - - - - MQTT Connection - - - - - + + - Serial Connection (GVRET) - - - true + socketcand (SocketCAN over Ethernet) @@ -64,10 +64,10 @@ - - + + - LAWICEL / SLCAN Serial + CANlogserver @@ -78,10 +78,17 @@ - - + + - CANlogserver + LAWICEL / SLCAN Serial + + + + + + + GS USB (CAN Bus Debugger, CANtact, etc)