diff --git a/connections/gvretserial.cpp b/connections/gvretserial.cpp index 6e163d7..96666d5 100644 --- a/connections/gvretserial.cpp +++ b/connections/gvretserial.cpp @@ -1,6 +1,611 @@ -#include "cvretserial.h" +#include +#include +#include +#include +#include -CVRetSerial::CVRetSerial() +#include "gvretserial.h" + +GVRetSerial::GVRetSerial(QString portName) : + CANConnection(portName, CANCon::GVRET_SERIAL, 2), + mThread(NULL) { + getQueue().setSize(2000); /*TODO add check on returned value */ + /* move ourself to the thread */ + moveToThread(&mThread); + qDebug() << "GVRetSerial()"; + + serial = NULL; + rx_state = IDLE; + rx_step = 0; + gotValidated = true; + isAutoRestart = false; + + txTimestampBasis = QDateTime::currentMSecsSinceEpoch(); + + readSettings(); +} + + +GVRetSerial::~GVRetSerial() +{ + stop(); + qDebug() << "~GVRetSerial()"; +} + + +void GVRetSerial::start() +{ + qDebug() << "enter thread"; + /* start thread */ + mThread.start(QThread::HighPriority); + /* connect device in thread context */ + connect(&mThread, SIGNAL(started()), this, SLOT(connectDevice())); + + /* start timer */ + connect(&mTimer, SIGNAL(timeout()), this, SLOT(handleTick())); + mTimer.setInterval(250); //tick four times per second + mTimer.setSingleShot(false); //keep ticking + mTimer.start(); +} + + +void GVRetSerial::suspend(bool pSuspend) { + /* make sure we execute in mThread context */ + if(QThread::currentThread() != &mThread) { + QMetaObject::invokeMethod(this, "suspend", + Qt::BlockingQueuedConnection, + Q_ARG(bool, pSuspend)); + return; + } + + /* update capSuspended */ + setCapSuspended(pSuspend); + + /* flush queue if we are suspended */ + if(isCapSuspended()) + getQueue().flush(); +} + + +void GVRetSerial::stop() { + mTimer.stop(); + mThread.quit(); + if(!mThread.wait()) { + qDebug() << "can't stop thread"; + } + disconnectDevice(); +} + + +bool GVRetSerial::getBusSettings(int pBusIdx, CANBus& pBus) { + /* make sure we execute in mThread context */ + if(QThread::currentThread() != &mThread) { + bool ret; + QMetaObject::invokeMethod(this, "getBusSettings", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, ret), + Q_ARG(int , pBusIdx), + Q_ARG(CANBus& , pBus)); + return ret; + } + + return getBusConfig(pBusIdx, pBus); +} + + +void GVRetSerial::sendFrame(const CANFrame *) {} +void GVRetSerial::sendFrameBatch(const QList *){} + + + +void GVRetSerial::readSettings() +{ + QSettings settings; + + if (settings.value("Main/ValidateComm", true).toBool()) + { + doValidation = true; + } + else doValidation = false; +} + + +void GVRetSerial::connectDevice() +{ + QSettings settings; + + /* disconnect device */ + if(serial) + disconnectDevice(); + + /* open new device */ + serial = new QSerialPort(QSerialPortInfo(getPort())); + if(!serial) { + qDebug() << "can't open serial port " << getPort(); + return; + } + + /* configure */ + serial->setDataBits(serial->Data8); + serial->setFlowControl(serial->HardwareControl); //this is important though + if (!serial->open(QIODevice::ReadWrite)) + { + qDebug() << serial->errorString(); + } + serial->setDataTerminalReady(true); //you do need to set these or the fan gets dirty + serial->setRequestToSend(true); + + + QByteArray output; + output.append((char)0xE7); //this puts the device into binary comm mode + output.append((char)0xE7); + + output.append((char)0xF1); //signal we want to issue a command + output.append((char)0x06); //request canbus stats from the board + + output.append((char)0xF1); //another command to the GVRET + output.append((char)0x07); //request device information + + output.append((char)0xF1); + output.append((char)0x08); //setting singlewire mode + if (settings.value("Main/SingleWireMode", false).toBool()) + { + output.append((char)0x10); //signal that we do want single wire mode + } + else + { + output.append((char)0xFF); //signal we don't want single wire mode + } + + output.append((char)0xF1); //yet another command + output.append((char)0x09); //comm validation command + + output.append((char)0xF1); //and another command + output.append((char)0x01); //Time Sync - Not implemented until 333 but we can try + + continuousTimeSync = true; + + serial->write(output); + + if(doValidation) { + QTimer::singleShot(1000, this, SLOT(connectionTimeout())); + } + else { + setStatus(CANCon::CONNECTED); + emit status(getStatus()); + } + + /* connect reading event */ + connect(serial, SIGNAL(readyRead()), this, SLOT(readSerialData())); +} + + +void GVRetSerial::disconnectDevice() { + if (serial != NULL) + { + if (serial->isOpen()) + { + serial->clear(); + serial->close(); + + } + serial->disconnect(); //disconnect all signals + delete serial; + serial = NULL; + } +} + + +void GVRetSerial::connectionTimeout() +{ + //one second after trying to connect are we actually connected? + if (CANCon::NOT_CONNECTED==getStatus()) //no? + { + //then emit the the failure signal and see if anyone cares + qDebug() << "Failed to connect to GVRET at that com port"; + + disconnectDevice(); + } +} + + +void GVRetSerial::readSerialData() +{ + QByteArray data = serial->readAll(); + unsigned char c; + //qDebug() << (tr("Got data from serial. Len = %0").arg(data.length())); + for (int i = 0; i < data.length(); i++) + { + c = data.at(i); + procRXChar(c); + } +} + + +void GVRetSerial::procRXChar(unsigned char c) +{ + switch (rx_state) + { + case IDLE: + if (c == 0xF1) rx_state = GET_COMMAND; + break; + case GET_COMMAND: + switch (c) + { + case 0: //receiving a can frame + rx_state = BUILD_CAN_FRAME; + rx_step = 0; + break; + case 1: //time sync + rx_state = TIME_SYNC; + rx_step = 0; + break; + case 2: //process a return reply for digital input states. + rx_state = GET_DIG_INPUTS; + rx_step = 0; + break; + case 3: //process a return reply for analog inputs + rx_state = GET_ANALOG_INPUTS; + break; + case 4: //we set digital outputs we don't accept replies so nothing here. + rx_state = IDLE; + break; + case 5: //we set canbus specs we don't accept replies. + rx_state = IDLE; + break; + case 6: //get canbus parameters from GVRET + rx_state = GET_CANBUS_PARAMS; + rx_step = 0; + break; + case 7: //get device info + rx_state = GET_DEVICE_INFO; + rx_step = 0; + break; + case 9: + gotValidated = true; + //qDebug() << "Got validated"; + rx_state = IDLE; + break; + } + break; + case BUILD_CAN_FRAME: + switch (rx_step) + { + case 0: + buildFrame.timestamp = c; + break; + case 1: + buildFrame.timestamp |= (uint)(c << 8); + break; + case 2: + buildFrame.timestamp |= (uint)c << 16; + break; + case 3: + buildFrame.timestamp |= (uint)c << 24; + break; + case 4: + buildFrame.ID = c; + break; + case 5: + buildFrame.ID |= c << 8; + break; + case 6: + buildFrame.ID |= c << 16; + break; + case 7: + buildFrame.ID |= c << 24; + if ((buildFrame.ID & 1 << 31) == 1 << 31) + { + buildFrame.ID &= 0x7FFFFFFF; + buildFrame.extended = true; + } + else buildFrame.extended = false; + break; + case 8: + buildFrame.len = c & 0xF; + if (buildFrame.len > 8) buildFrame.len = 8; + buildFrame.bus = (c & 0xF0) >> 4; + break; + default: + if (rx_step < buildFrame.len + 9) + { + buildFrame.data[rx_step - 9] = c; + } + else + { + rx_state = IDLE; + rx_step = 0; + buildFrame.isReceived = true; + + if (!isCapSuspended()) + { + /* get frame from queue */ + CANFrame* frame_p = getQueue().get(); + if(frame_p) { + /* copy frame */ + *frame_p = buildFrame; + /* enqueue frame */ + getQueue().queue(); + } + else + qDebug() << "can't get a frame, ERROR"; + + //take the time the frame came in and try to resync the time base. + if (continuousTimeSync) txTimestampBasis = QDateTime::currentMSecsSinceEpoch() - (buildFrame.timestamp / 1000); + } + } + break; + } + rx_step++; + break; + case TIME_SYNC: //gives a pretty good base guess for the proper timestamp. Can be refined when traffic starts to flow (if wanted) + switch (rx_step) + { + case 0: + buildTimeBasis = c; + break; + case 1: + buildTimeBasis += ((uint32_t)c << 8); + break; + case 2: + buildTimeBasis += ((uint32_t)c << 16); + break; + case 3: + buildTimeBasis += ((uint32_t)c << 24); + qDebug() << "GVRET firmware reports timestamp of " << buildTimeBasis; + txTimestampBasis = QDateTime::currentMSecsSinceEpoch() - ((uint64_t)buildTimeBasis / (uint64_t)1000ull); + continuousTimeSync = false; + rx_state = IDLE; + break; + } + rx_step++; + break; + + case GET_ANALOG_INPUTS: //get 9 bytes - 2 per analog input plus checksum + switch (rx_step) + { + case 0: + break; + } + rx_step++; + break; + case GET_DIG_INPUTS: //get two bytes. One for digital in status and one for checksum. + switch (rx_step) + { + case 0: + break; + case 1: + rx_state = IDLE; + break; + } + rx_step++; + break; + case GET_CANBUS_PARAMS: + switch (rx_step) + { + case 0: + can0Enabled = (c & 0xF); + can0ListenOnly = (c >> 4); + break; + case 1: + can0Baud = c; + break; + case 2: + can0Baud |= c << 8; + break; + case 3: + can0Baud |= c << 16; + break; + case 4: + can0Baud |= c << 24; + break; + case 5: + can1Enabled = (c & 0xF); + can1ListenOnly = (c >> 4); + deviceSingleWireMode = (c >> 6); + break; + case 6: + can1Baud = c; + break; + case 7: + can1Baud |= c << 8; + break; + case 8: + can1Baud |= c << 16; + break; + case 9: + can1Baud |= c << 24; + rx_state = IDLE; + qDebug() << "Baud 0 = " << can0Baud; + qDebug() << "Baud 1 = " << can1Baud; + + can0Baud |= 0x80000000; + if (can0Enabled) can0Baud |= 0x40000000; + if (can0ListenOnly) can0Baud |= 0x20000000; + + can1Baud |= 0x80000000; + if (can1Enabled) can1Baud |= 0x40000000; + if (can1ListenOnly) can1Baud |= 0x20000000; + if (deviceSingleWireMode > 0) can1Baud |= 0x10000000; + + setStatus(CANCon::CONNECTED); + emit status(getStatus()); + + int can0Status = 0x78; //updating everything we can update + int can1Status = 0x78; + if (can0Enabled) can0Status +=1; + if (can0ListenOnly) can0Status += 4; + if (can1Enabled) can1Status += 1; + if (deviceSingleWireMode > 0) can1Status += 2; + if (can1ListenOnly) can1Status += 4; + //emit busStatus(busBase, can0Baud & 0xFFFFF, can0Status); + //emit busStatus(busBase + 1, can1Baud & 0xFFFFF, can1Status); + break; + } + rx_step++; + break; + case GET_DEVICE_INFO: + switch (rx_step) + { + case 0: + deviceBuildNum = c; + break; + case 1: + deviceBuildNum |= c << 8; + break; + case 2: + break; //don't care about eeprom version + case 3: + break; //don't care about file type + case 4: + break; //don't care about whether it auto logs or not + case 5: + deviceSingleWireMode = c; + rx_state = IDLE; + qDebug() << "build num: " << deviceBuildNum; + qDebug() << "single wire can: " << deviceSingleWireMode; + emit deviceInfo(deviceBuildNum, deviceSingleWireMode); + break; + } + rx_step++; + break; + case SET_DIG_OUTPUTS: + rx_state = IDLE; + break; + case SETUP_CANBUS: + rx_state = IDLE; + break; + case SET_SINGLEWIRE_MODE: + rx_state = IDLE; + break; + + } +} + + +void GVRetSerial::handleTick() +{ + //qDebug() << "Tick!"; + + if( CANCon::CONNECTED == getStatus() ) + { + if (!gotValidated && doValidation) + { + if (serial == NULL) return; + if (serial->isOpen()) //if it's still false we have a problem... + { + qDebug() << "Comm validation failed. "; + + setStatus(CANCon::NOT_CONNECTED); + emit status(getStatus()); + + disconnectDevice(); //start by stopping everything. + //Then wait 500ms and restart the connection automatically + QTimer::singleShot(500, this, SLOT(connectDevice())); + return; + } + } + } + + if (doValidation && serial && serial->isOpen()) sendCommValidation(); +} + + +void GVRetSerial::sendCommValidation() +{ + QByteArray output; + + gotValidated = false; + output.append((char)0xF1); //another command to the GVRET + output.append((char)0x09); //request a reply to get validation + //send it twice for good measure. + output.append((char)0xF1); //another command to the GVRET + output.append((char)0x09); //request a reply to get validation + + serial->write(output); +} + + +void GVRetSerial::setBusSettings(int pBusIdx, CANBus bus) +{ + /* make sure we execute in mThread context */ + if(QThread::currentThread() != &mThread) { + QMetaObject::invokeMethod(this, "setBusSettings", + Qt::BlockingQueuedConnection, + Q_ARG(int, pBusIdx), + Q_ARG(CANBus, bus)); + return; + } + + /* sanity checks */ + if( (pBusIdx < 0) || pBusIdx >= getNumBuses()) + return; + + /* copy bus config */ + setBusConfig(pBusIdx, bus); + + qDebug() << "About to update bus " << pBusIdx << " on GVRET"; + if (pBusIdx == 0) + { + can0Baud = bus.getSpeed(); + can0Baud |= 0x80000000; + if (bus.isActive()) + { + can0Baud |= 0x40000000; + can0Enabled = true; + } + else can0Enabled = false; + + if (bus.isListenOnly()) + { + can0Baud |= 0x20000000; + can0ListenOnly = true; + } + else can0ListenOnly = false; + } + else if (pBusIdx == 1) + { + can1Baud = bus.getSpeed(); + can1Baud |= 0x80000000; + if (bus.isActive()) + { + can1Baud |= 0x40000000; + can1Enabled = true; + } + else can1Enabled = false; + + if (bus.isListenOnly()) + { + can1Baud |= 0x20000000; + can1ListenOnly = true; + } + else can1ListenOnly = false; + + if (bus.isSingleWire()) + { + can1Baud |= 0x10000000; + deviceSingleWireMode = 1; + } + else deviceSingleWireMode = 0; + } + + /* update baud rates */ + QByteArray buffer; + qDebug() << "Got signal to update bauds. 1: " << can0Baud <<" 2: " << can1Baud; + buffer[0] = (char)0xF1; //start of a command over serial + buffer[1] = 5; //setup canbus + buffer[2] = (unsigned char)(can0Baud & 0xFF); //four bytes of ID LSB first + buffer[3] = (unsigned char)(can0Baud >> 8); + buffer[4] = (unsigned char)(can0Baud >> 16); + buffer[5] = (unsigned char)(can0Baud >> 24); + buffer[6] = (unsigned char)(can1Baud & 0xFF); //four bytes of ID LSB first + buffer[7] = (unsigned char)(can1Baud >> 8); + buffer[8] = (unsigned char)(can1Baud >> 16); + buffer[9] = (unsigned char)(can1Baud >> 24); + buffer[10] = 0; + if (serial == NULL) return; + if (!serial->isOpen()) return; + serial->write(buffer); } diff --git a/connections/gvretserial.h b/connections/gvretserial.h index 12857c0..94f90e1 100644 --- a/connections/gvretserial.h +++ b/connections/gvretserial.h @@ -1,11 +1,113 @@ -#ifndef CVRETSERIAL_H -#define CVRETSERIAL_H +#ifndef GVRETSERIAL_H +#define GVRETSERIAL_H + +#include +#include +#include +#include + +/*************/ +#include +#include +/*************/ -class CVRetSerial +#include "canframemodel.h" +#include "canconnection.h" + + +namespace SERIALSTATE { + +enum STATE //keep this enum synchronized with the Arduino firmware project { -public: - CVRetSerial(); + IDLE, + GET_COMMAND, + BUILD_CAN_FRAME, + TIME_SYNC, + GET_DIG_INPUTS, + GET_ANALOG_INPUTS, + SET_DIG_OUTPUTS, + SETUP_CANBUS, + GET_CANBUS_PARAMS, + GET_DEVICE_INFO, + SET_SINGLEWIRE_MODE }; -#endif // CVRETSERIAL_H \ No newline at end of file +} + + +using namespace SERIALSTATE; +class GVRetSerial : public CANConnection +{ + Q_OBJECT + +public: + GVRetSerial(QString portName); + virtual ~GVRetSerial(); + + virtual void start(); + virtual void stop(); + + +signals: + void error(const QString &); + + void status(CANCon::status); + void deviceInfo(int, int); //First param = driver version (or version of whatever you want), second param a status byte + + //bus number, bus speed, status (bit 0 = enabled, 1 = single wire, 2 = listen only) + //3 = Use value stored for enabled, 4 = use value passed for single wire, 5 = use value passed for listen only + //6 = use value passed for speed. This allows bus status to be updated but set that some things aren't really + //being passed. Just set for things that really are being updated. + void busStatus(int, int, int); + + +public slots: + + virtual void sendFrame(const CANFrame *); + virtual void sendFrameBatch(const QList *); + + virtual void setBusSettings(int, CANBus); + virtual bool getBusSettings(int pBusIdx, CANBus& pBus); + + virtual void suspend(bool); + +protected: + void disconnectDevice(); + +private slots: + void connectDevice(); + void connectionTimeout(); + void readSerialData(); + void handleTick(); + +private: + void readSettings(); + void procRXChar(unsigned char); + void sendCommValidation(); + +protected: + QTimer mTimer; + QThread mThread; + + + bool doValidation; + bool gotValidated; + bool isAutoRestart; + bool continuousTimeSync; + QSerialPort *serial; + QMutex sendBulkMutex; + int framesRapid; + STATE rx_state; + int rx_step; + CANFrame buildFrame; + int can0Baud, can1Baud; + bool can0Enabled, can1Enabled; + bool can0ListenOnly, can1ListenOnly; + int deviceBuildNum; + int deviceSingleWireMode; + uint64_t txTimestampBasis; + uint32_t buildTimeBasis; +}; + +#endif // GVRETSERIAL_H