This further abstracts the interface. Things are reasonably stable now for frame reception. Initial work has been done to support frame transmission as well. This is currently less stable but mostly working.
571 lines
16 KiB
C++
571 lines
16 KiB
C++
#include "serialworker.h"
|
|
|
|
#include <QSerialPort>
|
|
#include <QDebug>
|
|
#include <QTimer>
|
|
#include <QSettings>
|
|
|
|
SerialWorker::SerialWorker(CANFrameModel *model, int base) : CANConnection(model, base)
|
|
{
|
|
qDebug() << "Serial Worker constructor";
|
|
serial = NULL;
|
|
rx_state = IDLE;
|
|
rx_step = 0;
|
|
buildFrame = new CANFrame;
|
|
ticker = NULL;
|
|
framesRapid = 0;
|
|
gotValidated = true;
|
|
isAutoRestart = false;
|
|
|
|
txTimestampBasis = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
readSettings();
|
|
}
|
|
|
|
SerialWorker::~SerialWorker()
|
|
{
|
|
if (serial != NULL)
|
|
{
|
|
if (serial->isOpen())
|
|
{
|
|
serial->clear();
|
|
serial->close();
|
|
|
|
}
|
|
serial->disconnect(); //disconnect all signals
|
|
delete serial;
|
|
}
|
|
if (ticker != NULL) ticker->stop();
|
|
}
|
|
|
|
void SerialWorker::run()
|
|
{
|
|
qDebug() << "Serial worker thread starting";
|
|
ticker = new QTimer;
|
|
connect(ticker, SIGNAL(timeout()), this, SLOT(handleTick()));
|
|
|
|
ticker->setInterval(250); //tick four times per second
|
|
ticker->setSingleShot(false); //keep ticking
|
|
ticker->start();
|
|
}
|
|
|
|
void SerialWorker::readSettings()
|
|
{
|
|
QSettings settings;
|
|
|
|
if (settings.value("Main/ValidateComm", true).toBool())
|
|
{
|
|
doValidation = true;
|
|
}
|
|
else doValidation = false;
|
|
//doValidation=false;
|
|
}
|
|
|
|
void SerialWorker::setSerialPort(QSerialPortInfo *port)
|
|
{
|
|
QSettings settings;
|
|
|
|
currentPort = port;
|
|
|
|
if (serial != NULL)
|
|
{
|
|
if (serial->isOpen())
|
|
{
|
|
serial->clear();
|
|
serial->close();
|
|
}
|
|
serial->disconnect(); //disconnect all signals
|
|
delete serial;
|
|
}
|
|
|
|
serial = new QSerialPort(*port);
|
|
|
|
qDebug() << "Serial port name is " << port->portName();
|
|
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) isConnected = false;
|
|
else isConnected = true;
|
|
connect(serial, SIGNAL(readyRead()), this, SLOT(readSerialData()));
|
|
if (doValidation) QTimer::singleShot(1000, this, SLOT(connectionTimeout()));
|
|
}
|
|
|
|
void SerialWorker::connectionTimeout()
|
|
{
|
|
//one second after trying to connect are we actually connected?
|
|
if (!isConnected) //no?
|
|
{
|
|
//then emit the the failure signal and see if anyone cares
|
|
qDebug() << "Failed to connect to GVRET at that com port";
|
|
//ticker->stop();
|
|
closeSerialPort(); //make sure it's properly closed anyway
|
|
emit connectionFailure();
|
|
}
|
|
}
|
|
|
|
void SerialWorker::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);
|
|
}
|
|
if (framesRapid > 0)
|
|
{
|
|
emit frameUpdateRapid(framesRapid);
|
|
framesRapid = 0;
|
|
}
|
|
}
|
|
|
|
void SerialWorker::sendFrame(const CANFrame *frame)
|
|
{
|
|
QByteArray buffer;
|
|
int c;
|
|
int ID;
|
|
CANFrame tempFrame = *frame;
|
|
tempFrame.isReceived = false;
|
|
tempFrame.timestamp = ((QDateTime::currentMSecsSinceEpoch() - txTimestampBasis) * 1000);
|
|
|
|
//qDebug() << "Sending out frame with id " << frame->ID;
|
|
|
|
//show our sent frames in the list too. This happens even if we're not connected.
|
|
model->addFrame(tempFrame, false);
|
|
framesRapid++;
|
|
|
|
if (serial == NULL) return;
|
|
if (!serial->isOpen()) return;
|
|
if (!isConnected) return;
|
|
|
|
ID = frame->ID;
|
|
if (frame->extended) ID |= 1 << 31;
|
|
|
|
buffer[0] = (char)0xF1; //start of a command over serial
|
|
buffer[1] = 0; //command ID for sending a CANBUS frame
|
|
buffer[2] = (unsigned char)(ID & 0xFF); //four bytes of ID LSB first
|
|
buffer[3] = (unsigned char)(ID >> 8);
|
|
buffer[4] = (unsigned char)(ID >> 16);
|
|
buffer[5] = (unsigned char)(ID >> 24);
|
|
buffer[6] = (unsigned char)((frame->bus - this->getBusBase()) & 1);
|
|
buffer[7] = (unsigned char)frame->len;
|
|
for (c = 0; c < frame->len; c++)
|
|
{
|
|
buffer[8 + c] = frame->data[c];
|
|
}
|
|
buffer[8 + frame->len] = 0;
|
|
|
|
//qDebug() << "writing " << buffer.length() << " bytes to serial port";
|
|
serial->write(buffer);
|
|
}
|
|
|
|
//a simple way for another thread to pass us a bunch of frames to send.
|
|
//Don't get carried away here. The GVRET firmware only has finite
|
|
//buffers and besides, the other end will get buried in traffic.
|
|
void SerialWorker::sendFrameBatch(const QList<CANFrame> *frames)
|
|
{
|
|
sendBulkMutex.lock();
|
|
for (int i = 0; i < frames->length(); i++) sendFrame(&frames->at(i));
|
|
sendBulkMutex.unlock();
|
|
}
|
|
|
|
void SerialWorker::updateBaudRates(int Speed1, int Speed2)
|
|
{
|
|
QByteArray buffer;
|
|
qDebug() << "Got signal to update bauds. 1: " << Speed1 <<" 2: " << Speed2;
|
|
buffer[0] = (char)0xF1; //start of a command over serial
|
|
buffer[1] = 5; //setup canbus
|
|
buffer[2] = (unsigned char)(Speed1 & 0xFF); //four bytes of ID LSB first
|
|
buffer[3] = (unsigned char)(Speed1 >> 8);
|
|
buffer[4] = (unsigned char)(Speed1 >> 16);
|
|
buffer[5] = (unsigned char)(Speed1 >> 24);
|
|
buffer[6] = (unsigned char)(Speed2 & 0xFF); //four bytes of ID LSB first
|
|
buffer[7] = (unsigned char)(Speed2 >> 8);
|
|
buffer[8] = (unsigned char)(Speed2 >> 16);
|
|
buffer[9] = (unsigned char)(Speed2 >> 24);
|
|
buffer[10] = 0;
|
|
if (serial == NULL) return;
|
|
if (!serial->isOpen()) return;
|
|
serial->write(buffer);
|
|
}
|
|
|
|
void SerialWorker::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;
|
|
//qDebug() << "emit from serial handler to main form id: " << buildFrame->ID;
|
|
//if (capturing)
|
|
//{
|
|
buildFrame->isReceived = true;
|
|
model->addFrame(*buildFrame, false);
|
|
//take the time the frame came in and try to resync the time base.
|
|
if (continuousTimeSync) txTimestampBasis = QDateTime::currentMSecsSinceEpoch() - (buildFrame->timestamp / 1000);
|
|
framesRapid++;
|
|
//}
|
|
}
|
|
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;
|
|
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;
|
|
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;
|
|
if (!can1Enabled) can1Baud = 0;
|
|
if (!can0Enabled) can0Baud = 0;
|
|
isConnected = true;
|
|
emit connectionSuccess();
|
|
emit busStatus(busBase, can0Baud, can0Enabled?0x49:0x48); //say that we're updating baud rate and set enabled status
|
|
emit busStatus(busBase + 1, can1Baud, can1Enabled?0x49:0x48);
|
|
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 SerialWorker::handleTick()
|
|
{
|
|
//qDebug() << "Tick!";
|
|
|
|
if (isConnected)
|
|
{
|
|
if (!gotValidated && doValidation)
|
|
{
|
|
if (serial == NULL) return;
|
|
if (serial->isOpen()) //if it's still false we have a problem...
|
|
{
|
|
qDebug() << "Comm validation failed. ";
|
|
closeSerialPort(); //start by stopping everything.
|
|
//Then wait 500ms and restart the connection automatically
|
|
QTimer::singleShot(500, this, SLOT(handleReconnect()));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (doValidation && serial && serial->isOpen()) sendCommValidation();
|
|
}
|
|
|
|
void SerialWorker::handleReconnect()
|
|
{
|
|
qDebug() << "Automatically reopening the connection";
|
|
setSerialPort(currentPort); //then go back through the re-init
|
|
}
|
|
|
|
void SerialWorker::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);
|
|
}
|
|
|
|
//totally shuts down the whole thing
|
|
void SerialWorker::closeSerialPort()
|
|
{
|
|
if (serial == NULL) return;
|
|
if (serial->isOpen())
|
|
{
|
|
serial->clear();
|
|
serial->close();
|
|
}
|
|
serial->disconnect();
|
|
//do not stop the ticker here. It always stays running now.
|
|
//ticker->stop();
|
|
delete serial;
|
|
serial = NULL;
|
|
}
|
|
|
|
/*
|
|
void SerialWorker::stopFrameCapture()
|
|
{
|
|
qDebug() << "Stopping frame capture";
|
|
capturing = false;
|
|
}
|
|
|
|
void SerialWorker::startFrameCapture()
|
|
{
|
|
qDebug() << "Starting up frame capture";
|
|
capturing = true;
|
|
}
|
|
*/
|
|
|
|
void SerialWorker::updatePortName(QString portName)
|
|
{
|
|
QList<QSerialPortInfo> ports;
|
|
|
|
CANConnection::updatePortName(portName);
|
|
|
|
ports = QSerialPortInfo::availablePorts();
|
|
|
|
for (int i = 0; i < ports.count(); i++)
|
|
{
|
|
if (portName == ports[i].portName())
|
|
{
|
|
setSerialPort(&ports[i]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int SerialWorker::getNumBuses()
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
QString SerialWorker::getConnTypeName()
|
|
{
|
|
return QString("GVRET");
|
|
}
|
|
|
|
void SerialWorker::updateBusSettings(CAN_Bus *bus)
|
|
{
|
|
int busNum = bus->busNum - busBase;
|
|
if (busNum < 0) return;
|
|
if (busNum >= numBuses) return;
|
|
qDebug() << "About to update bus " << busNum << " on GVRET";
|
|
if (busNum == 0) can0Baud = bus->getSpeed();
|
|
if (busNum == 1) can1Baud = bus->getSpeed();
|
|
updateBaudRates(can0Baud,can1Baud);
|
|
}
|