Files
GoldenCheetah/src/Computrainer.cpp

1016 lines
31 KiB
C++

/*
* Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net),
* Mark Liversedge (liversedge@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
// ISSUES
// ======
// 1. SpinScan decoding is not implemented (but erg and slope mode are)
// 2. Some C-style casts used for expediency
#include "Computrainer.h"
const static uint8_t ergo_command[56] = {
// Ergo various
// crc - - mode cmd val bits
// ---- ---- ---- ---- ---- ---- ----
0x6D, 0x00, 0x00, 0x0A, 0x08, 0x00, 0xE0,
0x65, 0x00, 0x00, 0x0A, 0x10, 0x00, 0xE0,
0x00, 0x00, 0x00, 0x0A, 0x18, 0x5D, 0xC1,
0x33, 0x00, 0x00, 0x0A, 0x24, 0x1E, 0xE0,
0x6A, 0x00, 0x00, 0x0A, 0x2C, 0x5F, 0xE0,
0x41, 0x00, 0x00, 0x0A, 0x34, 0x00, 0xE0,
0x2D, 0x00, 0x00, 0x0A, 0x38, 0x10, 0xC2,
0x03, 0x00, 0x00, 0x0A, 0x40, 0x32, 0xE0 // set LOAD command
};
const static uint8_t ss_command[56] = {
// Spinscan various
// crc - - mode cmd val bits
// ---- ---- ---- ---- ---- ---- ----
0x61, 0x00, 0x00, 0x16, 0x08, 0x00, 0xE0, // set GRADIENT command
0x59, 0x00, 0x00, 0x16, 0x10, 0x00, 0xE0, // set WINDSPEED (?)
0x74, 0x00, 0x00, 0x16, 0x18, 0x5D, 0xC1,
0x27, 0x00, 0x00, 0x16, 0x24, 0x1E, 0xE0,
0x5E, 0x00, 0x00, 0x16, 0x2C, 0x5F, 0xE0,
0x35, 0x00, 0x00, 0x16, 0x34, 0x00, 0xE0,
0x21, 0x00, 0x00, 0x16, 0x38, 0x10, 0xC2,
0x29, 0x00, 0x00, 0x16, 0x40, 0x00, 0xE0
};
const static uint8_t rrc_command[56] = {
0x31, 0x00, 0x00, 0x0e, 0x40, 0x00, 0xe0,
0x69, 0x00, 0x00, 0x0e, 0x08, 0x00, 0xe0,
0x61, 0x00, 0x00, 0x0e, 0x10, 0x00, 0xe0,
0x78, 0x00, 0x00, 0x0e, 0x18, 0x61, 0xc1,
0x4d, 0x00, 0x00, 0x0e, 0x24, 0x00, 0xe0,
0x6e, 0x00, 0x00, 0x0e, 0x2c, 0x57, 0xc1,
0x3d, 0x00, 0x00, 0x0e, 0x34, 0x00, 0xe0,
0x23, 0x00, 0x00, 0x0e, 0x38, 0x16, 0xc2
};
/* ----------------------------------------------------------------------
* CONSTRUCTOR/DESRTUCTOR
* ---------------------------------------------------------------------- */
Computrainer::Computrainer(QObject *parent, QString devname) : QThread(parent)
{
devicePower = deviceHeartRate = deviceCadence = deviceSpeed = deviceRRC = 0.00;
for (int i=0; i<24; spinScan[i++] = 0) ;
mode = DEFAULT_MODE;
load = DEFAULT_LOAD;
gradient = DEFAULT_GRADIENT;
deviceCalibrated = false;
deviceHRConnected = false;
deviceCADConnected = false;
setDevice(devname);
deviceStatus=0;
this->parent = parent;
/* 56 byte control sequence, composed of 8 command packets
* where the last packet sets the load. The first byte
* is a CRC for the value being issued (e.g. Load in WATTS)
*
* these members are modified as load / gradient are set
*/
memcpy(ERGO_Command, ergo_command, 56);
memcpy(SS_Command, ss_command, 56);
}
Computrainer::~Computrainer()
{
}
/* ----------------------------------------------------------------------
* SET
* ---------------------------------------------------------------------- */
void Computrainer::setDevice(QString devname)
{
// if not null, replace existing if set, otherwise set
deviceFilename = devname;
}
void Computrainer::setMode(int mode, double load, double gradient)
{
pvars.lock();
this->mode = mode;
this->load = load;
this->gradient = gradient;
pvars.unlock();
}
void Computrainer::setLoad(double load)
{
pvars.lock();
if (load > 1500) load = 1500;
if (load < 50) load = 50;
this->load = load;
pvars.unlock();
}
void Computrainer::setGradient(double gradient)
{
pvars.lock();
this->gradient = gradient;
pvars.unlock();
}
/* ----------------------------------------------------------------------
* GET
* ---------------------------------------------------------------------- */
bool Computrainer::isHRConnected()
{
bool tmp;
pvars.lock();
tmp = deviceHRConnected;
pvars.unlock();
return tmp;
}
bool Computrainer::isCADConnected()
{
bool tmp;
pvars.lock();
tmp = deviceCADConnected;
pvars.unlock();
return tmp;
}
bool Computrainer::isCalibrated()
{
bool tmp;
pvars.lock();
tmp = deviceCalibrated;
pvars.unlock();
return tmp;
}
void Computrainer::getTelemetry(double &power, double &heartrate, double &cadence, double &speed,
double &RRC, bool &calibration, int &buttons, uint8_t *ss, int &status)
{
pvars.lock();
power = devicePower;
heartrate = deviceHeartRate;
cadence = deviceCadence;
speed = deviceSpeed;
RRC = deviceRRC;
calibration = deviceCalibrated;
buttons = deviceButtons;
status = deviceStatus;
memcpy((void*)ss, (void*)spinScan, 24);
// work around to ensure controller doesn't miss button press.
// The run thread will only set the button bits, they don't get
// reset until the ui reads the device state
// Borrowed from: Fortius.cpp
deviceButtons = 0;
pvars.unlock();
}
void Computrainer::getSpinScan(double spinData[])
{
pvars.lock();
for (int i=0; i<24; spinData[i] = this->spinScan[i]) ;
pvars.unlock();
}
int Computrainer::getMode()
{
int tmp;
pvars.lock();
tmp = mode;
pvars.unlock();
return tmp;
}
double Computrainer::getLoad()
{
double tmp;
pvars.lock();
tmp = load;
pvars.unlock();
return tmp;
}
double Computrainer::getGradient()
{
double tmp;
pvars.lock();
tmp = gradient;
pvars.unlock();
return tmp;
}
/*----------------------------------------------------------------------
* COMPUTRAINER PROTOCOL DECODE/ENCODE ROUTINES
* (The clever stuff)
*
* Many thanks to Stephan Mantler for sharing some of his initial
* findings and source code.
*
* -- NOTE --
*
* ALTHOUGH THESE ROUTINES READ AND WRITE FROM/TO THE LOW LEVEL
* IO BUFFERS THEY *DO NOT* WRITE TO THE CLASS MEMBERS DIRECTLY
* THIS IS TO MINIMISE RACE CONDITIONS. THE EXECUTIVE FUNCTIONS
* ESPECIALLY run() CONTROL ACCESS TO THESE AND CACHE VALUES IN
* LOCAL VARS TO MINIMISE THIS.
*
* ** IF YOU MODIFY THIS CODE PLEASE BEAR THIS IN MIND **
*
* The protocol definition and reference code has been put up at
* https://www.sourceforge.net/projects/ctmac
*
* void prepareCommand() - sets up the command packet according to current settings
* int calcCRC(double value) - calculates the checksum for the current command
* int unpackTelemetry() - unpacks from CT protocol layout to byte layout
* returns non-zero if unknown message type
*
*---------------------------------------------------------------------- */
int
Computrainer::start()
{
QThread::start();
return 0;
}
void Computrainer::prepareCommand(int mode, double value)
{
// prepare the control message according to the current mode and gradient/load
int load, gradient, crc;
switch (mode) {
case CT_ERGOMODE :
load = (int)value;
crc = calcCRC(load);
// BYTE 0 - 49 is b0, 53 is b4, 54 is b5, 55 is b6
ERGO_Command[49] = crc >> 1; // set byte 0
// BYTE 4 - command and highbyte
ERGO_Command[53] = 0x40; // set command
ERGO_Command[53] |= (load&(2048+1024+512)) >> 9;
// BYTE 5 - low 7
ERGO_Command[54] = 0;
ERGO_Command[54] |= (load&(128+64+32+16+8+4+2)) >> 1;
// BYTE 6 - sync + z set
ERGO_Command[55] = 128+64;
// low bit of supplement in bit 6 (32)
ERGO_Command[55] |= crc & 1 ? 32 : 0;
// Bit 2 (0x02) is low bit of high byte in load (bit 9 0x256)
ERGO_Command[55] |= (load&256) ? 2 : 0;
// Bit 1 (0x01) is low bit of low byte in load (but 1 0x01)
ERGO_Command[55] |= load&1;
break;
case CT_SSMODE :
if (value < -9.9) gradient = -99;
else if (value > 15) gradient = 150;
else gradient = value *10; // gradient is passed as an integer
// negative gradients use two's complement with bit 2048 set
if (gradient < 0) {
gradient *= -1; // make it positive
gradient &= 1024+512+256+128+64+32+16+8+4+2+1; // the number
gradient = ~gradient; // two's complement
}
crc = calcCRC(gradient);
// BYTE 0 - 49 is b0, 53 is b4, 54 is b5, 55 is b6
SS_Command[0] = crc >> 1; // set byte 0
// BYTE 4 - command and highbyte
SS_Command[4] = 0x08; // set command - slope set
SS_Command[4] |= (gradient&(2048+1024+512)) >> 9;
// BYTE 5 - low 7
SS_Command[5] = 0;
SS_Command[5] |= (gradient&(128+64+32+16+8+4+2)) >> 1;
// BYTE 6 - sync + z set
SS_Command[6] = 128+64;
// low bit of supplement in bit 6 (32)
SS_Command[6] |= crc & 1 ? 32 : 0;
// Bit 2 (0x02) is low bit of high byte in load (bit 9 0x256)
SS_Command[6] |= (gradient&256) ? 2 : 0;
// Bit 1 (0x01) is low bit of low byte in load (but 1 0x01)
SS_Command[6] |= gradient&1;
break;
}
}
// thanks to Sean Rhea for working this one out!
int Computrainer::calcCRC(int value)
{
return (0xff & (107 - (value & 0xff) - (value >> 8)));
}
// funny, just a few lines of code. oh the pain to get this working :-)
void Computrainer::unpackTelemetry(int &ss1, int &ss2, int &ss3, int &buttons, int &type, int &value8, int &value12)
{
static uint8_t ss[24];
static int pos=0;
// inbound data is in the 7 byte array Computrainer::buf[]
// for code clarity they hjave been put into these holdiing
// variables. the overhead is minimal and makes the code a
// lot easier to decipher! :-)
short s1 = buf[0]; // ss data
short s2 = buf[1]; // ss data
short s3 = buf[2]; // ss data
short bt = buf[3]; // button data
short b1 = buf[4]; // message and value
short b2 = buf[5]; // value
short b3 = buf[6]; // the dregs (sync, z and lsb for all the others)
// ss vars
ss1 = s1<<1 | (b3&32)>>5;
ss2 = s2<<1 | (b3&16)>>4;
ss3 = s3<<1 | (b3&8)>>3;
// swap nibbles, is this really right?
ss1 = ((ss1&15)<<4) | (ss1&240)>>4;
ss2 = ((ss2&15)<<4) | (ss2&240)>>4;
ss3 = ((ss3&15)<<4) | (ss3&240)>>4;
// buttons
buttons = bt<<1 | (b3&4)>>2;
// 4-bit message type
type = (b1&120)>>3;
// 8 bit value
value8 = (b2&~128)<<1 | (b3&1); // 8 bit values
// 12 bit value
value12 = value8 | (b1&7)<<9 | (b3&2)<<7;
if (buttons&64) {
memcpy((uint8_t*)spinScan, (uint8_t*)ss+3, 21);
memcpy((uint8_t*)spinScan+21, (uint8_t*)ss, 3);
//for (pos=0; pos<24; pos++) fprintf(stderr, "%d, ", ss[pos]);
//fprintf(stderr, "\n");
pos=0;
}
if (ss1 || ss2 || ss3) {
// we drop the msb and do a ones compliment, but
// that looks eerily like a signed byte.
// suspect there is more hidden in there?
// but when decoded as a signed byte the numbers
// are all over the place. investigate further!
ss[pos++] = 127^(ss1&127);
ss[pos++] = 127^(ss2&127);
ss[pos++] = 127^(ss3&127);
}
}
/* ----------------------------------------------------------------------
* EXECUTIVE FUNCTIONS
*
* start() - start/re-start reading telemetry in a thread
* stop() - stop reading telemetry and terminates thread
* pause() - discards inbound telemetry (ignores it)
*
*
* THE MEAT OF THE CODE IS IN RUN() IT IS A WHILE LOOP CONSTANTLY
* READING TELEMETRY AND ISSUING CONTROL COMMANDS WHILST UPDATING
* MEMBER VARIABLES AS TELEMETRY CHANGES ARE FOUND.
*
* run() - bg thread continuosly reading/writing the device port
* it is kicked off by start and then examines status to check
* when it is time to pause or stop altogether.
* ---------------------------------------------------------------------- */
int Computrainer::restart()
{
int status;
// get current status
pvars.lock();
status = this->deviceStatus;
pvars.unlock();
// what state are we in anyway?
if (status&CT_RUNNING && status&CT_PAUSED) {
status &= ~CT_PAUSED;
pvars.lock();
this->deviceStatus = status;
pvars.unlock();
return 0; // ok its running again!
}
return 2;
}
int Computrainer::stop()
{
// what state are we in anyway?
pvars.lock();
deviceStatus = 0; // Terminate it!
pvars.unlock();
return 0;
}
int Computrainer::pause()
{
int status;
// get current status
pvars.lock();
status = this->deviceStatus;
pvars.unlock();
if (status&CT_PAUSED) return 2; // already paused you muppet!
else if (!(status&CT_RUNNING)) return 4; // not running anyway, fool!
else {
// ok we're running and not paused so lets pause
status |= CT_PAUSED;
pvars.lock();
this->deviceStatus = status;
pvars.unlock();
return 0;
}
}
// used by thread to set variables and emit event if needed
// on unexpected exit
int Computrainer::quit(int code)
{
// event code goes here!
exit(code);
return 0; // never gets here obviously but shuts up the compiler!
}
/*----------------------------------------------------------------------
* THREADED CODE - READS TELEMETRY AND SENDS COMMANDS TO KEEP CT ALIVE
*----------------------------------------------------------------------*/
void Computrainer::run()
{
// locally cached settings - only update main class variables
// when they change
int cmds=0; // count loops with no command sent
// holders for unpacked telemetry
int ss1,ss2,ss3, buttons, type, value8, value12;
// newly read values - compared against cached values
int newmode;
double newload, newgradient;
double newspeed, newRRC;
bool newhrconnected, newcadconnected;
bool isDeviceOpen = false;
// Cached current values
// when new values are received from the device
// if they differ from current values we update
// otherwise do nothing
int curmode, curstatus;
double curload, curgradient;
double curPower; // current output power in Watts
double curHeartRate; // current heartrate in BPM
double curCadence; // current cadence in RPM
double curSpeed; // current speef in KPH
double curRRC; // calibrated Rolling Resistance
bool curhrconnected; // is HR sensor connected?
bool curcadconnected; // is CAD sensor connected?
int curButtons; // Button status
// initialise local cache & main vars
pvars.lock();
this->deviceStatus = CT_RUNNING;
curmode = this->mode;
curload = this->load;
curgradient = this->gradient;
curPower = this->devicePower = 0;
curHeartRate = this->deviceHeartRate = 0;
curCadence = this->deviceCadence = 0;
curSpeed = this->deviceSpeed = 0;
curButtons = this->deviceButtons = 0;
curRRC = this->deviceRRC = 0;
this->deviceCalibrated = false;
curhrconnected = false;
this->deviceHRConnected = false;
curcadconnected = false;
this->deviceCADConnected = false;
pvars.unlock();
// open the device
if (openPort()) {
quit(2);
return; // open failed!
} else {
isDeviceOpen = true;
}
// send first command to get computrainer ready
prepareCommand(curmode, curmode == CT_ERGOMODE ? curload : curgradient);
if (sendCommand(curmode) == -1) {
// send failed - ouch!
closePort(); // need to release that file handle!!
quit(4);
return; // couldn't write to the device
}
while(1) {
if (isDeviceOpen == true) {
if (readMessage() > 0) {
//----------------------------------------------------------------
// UPDATE BASIC TELEMETRY (HR, CAD, SPD et al)
//----------------------------------------------------------------
unpackTelemetry(ss1, ss2, ss3, buttons, type, value8, value12);
switch (type) {
case CT_HEARTRATE :
if (value8 != curHeartRate) {
curHeartRate = value8;
pvars.lock();
this->deviceHeartRate = curHeartRate;
pvars.unlock();
}
break;
case CT_POWER :
if (value12 != curPower) {
curPower = value12;
pvars.lock();
this->devicePower = curPower;
pvars.unlock();
}
break;
case CT_CADENCE :
if (value8 != curCadence) {
curCadence = value8;
pvars.lock();
this->deviceCadence = curCadence;
pvars.unlock();
}
break;
case CT_SPEED :
value12 *=36; // convert from mps to kph
value12 *=9;
value12 /=10; // it seems that compcs takes off 10% ????
newspeed = value12;
newspeed /= 1000;
if (newspeed != curSpeed) {
pvars.lock();
this->deviceSpeed = curSpeed = newspeed;
pvars.unlock();
}
break;
case CT_RRC :
//newcalibrated = value12&2048 ? true : false;
newRRC = value12&~2048; // only use 11bits
newRRC /= 256;
if (newRRC != curRRC) {
pvars.lock();
this->deviceRRC = curRRC = newRRC;
pvars.unlock();
}
break;
case CT_SENSOR :
newcadconnected = value12&2048 ? true : false;
newhrconnected = value12&1024 ? true : false;
if (newhrconnected != curhrconnected || newcadconnected != curcadconnected) {
pvars.lock();
this->deviceHRConnected=curhrconnected=newhrconnected;
this->deviceCADConnected=curcadconnected=newcadconnected;
pvars.unlock();
}
break;
default :
break;
}
//----------------------------------------------------------------
// UPDATE BUTTONS
//----------------------------------------------------------------
if (buttons != curButtons) {
// let the gui workout what the deal is with silly button values!
pvars.lock();
this->deviceButtons |= buttons; // Borrowed from Fortius.cpp: workaround to ensure controller doesn't miss button pushes
pvars.unlock();
}
//----------------------------------------------------------------
// UPDATE SSCAN
//----------------------------------------------------------------
/* not yet implemented */
} else {
// no data
// how long to sleep for ... mmm save CPU cycles vs
// data overflow ?
CTsleeper::msleep (100); // lets try a tenth of a second
}
}
//----------------------------------------------------------------
// LISTEN TO GUI CONTROL COMMANDS
//----------------------------------------------------------------
pvars.lock();
curstatus = this->deviceStatus;
newmode = this->mode;
newload = this->load;
curgradient = newgradient = this->gradient;
pvars.unlock();
/* time to shut up shop */
if (!(curstatus&CT_RUNNING)) {
// time to stop!
closePort(); // need to release that file handle!!
quit(0);
return;
}
if ((curstatus&CT_PAUSED) && isDeviceOpen == true) {
closePort();
isDeviceOpen = false;
} else if (!(curstatus&CT_PAUSED) && (curstatus&CT_RUNNING) && isDeviceOpen == false) {
if (openPort()) {
quit(2);
return; // open failed!
}
isDeviceOpen = true;
// send first command to get computrainer ready
prepareCommand(curmode, curmode == CT_ERGOMODE ? curload : curgradient);
if (sendCommand(curmode) == -1) {
// send failed - ouch!
closePort(); // need to release that file handle!!
quit(4);
return; // couldn't write to the device
}
}
//----------------------------------------------------------------
// KEEP THE COMPUTRAINER CONTROL ALIVE
//----------------------------------------------------------------
if (isDeviceOpen == true && !(cmds%10)) {
cmds=1;
curmode = newmode;
curload = newload;
curgradient = newgradient;
prepareCommand(curmode, curmode == CT_ERGOMODE ? curload : curgradient);
if (sendCommand(curmode) == -1) {
// send failed - ouch!
closePort(); // need to release that file handle!!
quit(4);
cmds=20;
return; // couldn't write to the device
}
} else {
cmds++;
}
}
}
void CTsleeper::msleep(unsigned long msecs)
{
QThread::msleep(msecs);
}
/* ----------------------------------------------------------------------
* LOW LEVEL DEVICE IO ROUTINES - PORT TO QIODEVICE REQUIRED BEFORE COMMIT
*
*
* HIGH LEVEL IO
* int sendCommand() - writes a command to the device
* int readMessage() - reads an inbound message
*
* LOW LEVEL IO
* openPort() - opens serial device and configures it
* closePort() - closes serial device and releases resources
* rawRead() - non-blocking read of inbound data
* rawWrite() - non-blocking write of outbound data
* discover() - check if a ct is attached to the port specified
* ---------------------------------------------------------------------- */
int Computrainer::sendCommand(int mode) // writes a command to the device
{
switch (mode) {
case CT_ERGOMODE :
return rawWrite(ERGO_Command, 56);
break;
case CT_SSMODE :
return rawWrite(SS_Command, 56);
break;
case CT_CALIBRATE :
return rawWrite(const_cast<uint8_t*>(rrc_command), 56);
break;
default :
return -1;
break;
}
}
int Computrainer::readMessage()
{
int rc;
if ((rc = rawRead(buf, 7)) > 0 && (buf[6]&128) == 0) {
// we got something but need to sync
while ((buf[6]&128) == 0 && rc > 0) {
rc = rawRead(&buf[6], 1);
}
// at this point we are synced, we may have a dodgy
// record but that is fair enough if we were out of
// sync anyway - the alternative is to keep going
// until we get a good message and that will
// lead to bigger issues (plus we may have a hw
// problem anyway).
//
// From experience, the need to sync is quite rare
// on a normally configured and working system
}
return rc;
}
int Computrainer::closePort()
{
#ifdef WIN32
return (int)!CloseHandle(devicePort);
#else
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
return close(devicePort);
#endif
}
int Computrainer::openPort()
{
#ifndef WIN32
// LINUX AND MAC USES TERMIO / IOCTL / STDIO
#if defined(Q_OS_MACX)
int ldisc=TTYDISC;
#else
int ldisc=N_TTY; // LINUX
#endif
if ((devicePort=open(deviceFilename.toAscii(),O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) return errno;
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
if (ioctl(devicePort, TIOCSETD, &ldisc) == -1) return errno;
// get current settings for the port
tcgetattr(devicePort, &deviceSettings);
// set raw mode i.e. ignbrk, brkint, parmrk, istrip, inlcr, igncr, icrnl, ixon
// noopost, cs8, noecho, noechonl, noicanon, noisig, noiexn
cfmakeraw(&deviceSettings);
cfsetspeed(&deviceSettings, B2400);
// further attributes
deviceSettings.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ICANON | ISTRIP | IXON | IXOFF | IXANY);
deviceSettings.c_iflag |= IGNPAR;
deviceSettings.c_cflag &= (~CSIZE & ~CSTOPB);
deviceSettings.c_oflag=0;
#if defined(Q_OS_MACX)
deviceSettings.c_cflag &= (~CCTS_OFLOW & ~CRTS_IFLOW); // no hardware flow control
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
#else
deviceSettings.c_cflag &= (~CRTSCTS); // no hardware flow control
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
#endif
deviceSettings.c_lflag=0;
deviceSettings.c_cc[VSTART] = 0x11;
deviceSettings.c_cc[VSTOP] = 0x13;
deviceSettings.c_cc[VEOF] = 0x20;
deviceSettings.c_cc[VMIN]=0;
deviceSettings.c_cc[VTIME]=0;
// set those attributes
if(tcsetattr(devicePort, TCSANOW, &deviceSettings) == -1) return errno;
tcgetattr(devicePort, &deviceSettings);
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
#else
// WINDOWS USES SET/GETCOMMSTATE AND READ/WRITEFILE
COMMTIMEOUTS timeouts; // timeout settings on serial ports
// if deviceFilename references a port above COM9
// then we need to open "\\.\COMX" not "COMX"
QString portSpec;
int portnum = deviceFilename.midRef(3).toString().toInt();
if (portnum < 10)
portSpec = deviceFilename;
else
portSpec = "\\\\.\\" + deviceFilename;
wchar_t deviceFilenameW[32]; // \\.\COM32 needs 9 characters, 32 should be enough?
MultiByteToWideChar(CP_ACP, 0, portSpec.toAscii(), -1, (LPWSTR)deviceFilenameW,
sizeof(deviceFilenameW));
// win32 commport API
devicePort = CreateFile (deviceFilenameW, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_DELETE|FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (devicePort == INVALID_HANDLE_VALUE) return -1;
if (GetCommState (devicePort, &deviceSettings) == false) return -1;
// so we've opened the comm port lets set it up for
deviceSettings.BaudRate = CBR_2400;
deviceSettings.fParity = NOPARITY;
deviceSettings.ByteSize = 8;
deviceSettings.StopBits = ONESTOPBIT;
deviceSettings.XonChar = 11;
deviceSettings.XoffChar = 13;
deviceSettings.EofChar = 0x0;
deviceSettings.ErrorChar = 0x0;
deviceSettings.EvtChar = 0x0;
deviceSettings.fBinary = true;
deviceSettings.fOutX = 0;
deviceSettings.fInX = 0;
deviceSettings.XonLim = 0;
deviceSettings.XoffLim = 0;
deviceSettings.fRtsControl = RTS_CONTROL_ENABLE;
deviceSettings.fDtrControl = DTR_CONTROL_ENABLE;
deviceSettings.fOutxCtsFlow = FALSE; //TRUE;
if (SetCommState(devicePort, &deviceSettings) == false) {
CloseHandle(devicePort);
return -1;
}
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutConstant = 1000;
timeouts.ReadTotalTimeoutMultiplier = 50;
timeouts.WriteTotalTimeoutConstant = 2000;
timeouts.WriteTotalTimeoutMultiplier = 0;
SetCommTimeouts(devicePort, &timeouts);
#endif
// success
return 0;
}
int Computrainer::rawWrite(uint8_t *bytes, int size) // unix!!
{
int rc=0;
#ifdef WIN32
DWORD cBytes;
rc = WriteFile(devicePort, bytes, size, &cBytes, NULL);
if (!rc) return -1;
return rc;
#else
int ibytes;
ioctl(devicePort, FIONREAD, &ibytes);
// timeouts are less critical for writing, since vols are low
rc= write(devicePort, bytes, size);
// but it is good to avoid buffer overflow since the
// computrainer microcontroller has almost no RAM
if (rc != -1) tcdrain(devicePort); // wait till its gone.
ioctl(devicePort, FIONREAD, &ibytes);
#endif
return rc;
}
int Computrainer::rawRead(uint8_t bytes[], int size)
{
int rc=0;
#ifdef WIN32
Q_UNUSED(size);
// Readfile deals with timeouts and readyread issues
DWORD cBytes;
rc = ReadFile(devicePort, bytes, 7, &cBytes, NULL);
if (rc) return (int)cBytes;
else return (-1);
#else
int timeout=0, i=0;
uint8_t byte;
// read one byte at a time sleeping when no data ready
// until we timeout waiting then return error
for (i=0; i<size; i++) {
timeout =0;
rc=0;
while (rc==0 && timeout < CT_READTIMEOUT) {
rc = read(devicePort, &byte, 1);
if (rc == -1) return -1; // error!
else if (rc == 0) {
msleep(50); // sleep for 1/20th of a second
timeout += 50;
} else {
bytes[i] = byte;
}
}
if (timeout >= CT_READTIMEOUT) return -1; // we timed out!
}
return i;
#endif
}
// check to see of there is a port at the device specified
// returns true if the device exists and false if not
bool Computrainer::discover(QString filename)
{
uint8_t *greeting = (uint8_t *)"RacerMate";
uint8_t handshake[7];
int rc;
if (filename.isEmpty()) return false; // no null filenames thanks
// lets set the port
setDevice(filename);
// lets open it
openPort();
// send a probe
if ((rc=rawWrite(greeting, 9)) == -1) {
closePort();
return false;
}
// did we get something back from the device?
if ((rc=rawRead(handshake, 6)) < 6) {
closePort();
return false;
}
closePort();
handshake[6] = '\0';
if (strcmp((char *)handshake, "LinkUp")) return false;
else return true;
}