Files
GoldenCheetah/src/PowerTapDevice.cpp
Mark Liversedge aa8605e8d5 QT5 -- 1 of 3
Porting the codebase to QT 5 (5.2) to get the
latest bug fixes, performance and improved platform
support.

This first part is to fixup the codebase to compile
on Qt 5, but some aspects have been broken (video).

The second part is to migrate from Qwt 6.0.1 to the
latest Qwt for multiaxis support.

The third part will be to fixup any platform specific
issues or issues identified at runtime.
2013-12-09 09:57:13 +00:00

330 lines
9.7 KiB
C++

/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net)
*
* 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
*/
#include "PowerTapDevice.h"
#include "PowerTapUtil.h"
#include <math.h>
#include "assert.h"
#define PT_DEBUG false
static bool powerTapRegistered =
Devices::addType("PowerTap", DevicesPtr(new PowerTapDevices()) );
DevicePtr
PowerTapDevices::newDevice( CommPortPtr dev )
{
return DevicePtr( new PowerTapDevice( dev ));
}
QString
PowerTapDevices::downloadInstructions() const
{
return (tr("Make sure the PowerTap unit is turned\n"
"on and that its display says, \"Host\""));
}
static bool
hasNewline(const char *buf, int len)
{
static char newline[] = { 0x0d, 0x0a };
if (len < 2)
return false;
for (int i = 0; i < len; ++i) {
bool success = true;
for (int j = 0; j < 2; ++j) {
if (buf[i+j] != newline[j]) {
success = false;
break;
}
}
if (success)
return true;
}
return false;
}
static QString
cEscape(const char *buf, int len)
{
char *result = new char[4 * len + 1];
char *tmp = result;
for (int i = 0; i < len; ++i) {
if (buf[i] == '"')
tmp += sprintf(tmp, "\\\"");
else if (isprint(buf[i]))
*(tmp++) = buf[i];
else
tmp += sprintf(tmp, "\\x%02x", 0xff & (unsigned) buf[i]);
}
return result;
}
bool
PowerTapDevice::doWrite(CommPortPtr dev, char c, bool hwecho, QString &err)
{
if (PT_DEBUG) printf("writing '%c' to device\n", c);
int n = dev->write(&c, 1, err);
if (n != 1) {
if (n < 0)
err = QString(tr("failed to write %1 to device: %2")).arg(c).arg(err);
else
err = QString(tr("timeout writing %1 to device")).arg(c);
return false;
}
if (hwecho) {
char c;
int n = dev->read(&c, 1, err);
if (n != 1) {
if (n < 0)
err = QString(tr("failed to read back hardware echo: %2")).arg(err);
else
err = tr("timeout reading back hardware echo");
return false;
}
}
return true;
}
int
PowerTapDevice::readUntilNewline(CommPortPtr dev, char *buf, int len, QString &err)
{
int sofar = 0;
while (!hasNewline(buf, sofar)) {
assert(sofar < len);
// Read one byte at a time to avoid waiting for timeout.
int n = dev->read(buf + sofar, 1, err);
if (n <= 0) {
err = (n < 0) ? (tr("read error: ") + err) : tr("read timeout");
err += QString(tr(", read %1 bytes so far: \"%2\""))
.arg(sofar).arg(cEscape(buf, sofar));
return -1;
}
sofar += n;
}
return sofar;
}
bool
PowerTapDevice::download( const QDir &tmpdir,
QList<DeviceDownloadFile> &files,
QString &err)
{
if (!dev->open(err)) {
err = tr("ERROR: open failed: ") + err;
return false;
}
// make several attempts at reading the version
int attempts = 3;
int veridx = -1;
int version_len;
char vbuf[256];
QByteArray version;
do {
if (!doWrite(dev, 0x56, false, err)) // 'V'
return false;
emit updateStatus( tr("Reading version...") );
if(m_Cancelled) {
err = tr("download cancelled");
return false;
}
version_len = readUntilNewline(dev, vbuf, sizeof(vbuf), err);
if (version_len < 0) {
err = tr("Error reading version: ") + err;
return false;
}
if (PT_DEBUG) {
printf("read version \"%s\"\n",
cEscape(vbuf, version_len).toLatin1().constData());
}
version = QByteArray(vbuf, version_len);
// We expect the version string to be something like
// "VER 02.21 PRO...", so if we see two V's, it's probably
// because there's a hardware echo going on.
veridx = version.indexOf("VER");
} while ((--attempts > 0) && (veridx < 0));
if (veridx < 0) {
err = QString(tr("Unrecognized version \"%1\""))
.arg(cEscape(vbuf, version_len));
return false;
}
bool hwecho = version.indexOf('V') < veridx;
if (PT_DEBUG) printf("hwecho=%s\n", hwecho ? "true" : "false");
emit updateStatus( tr("Reading header...") );
if(m_Cancelled) {
err = tr("download cancelled");
return false;
}
if (!doWrite(dev, 0x44, hwecho, err)) // 'D'
return false;
unsigned char header[6];
int header_len = dev->read(header, sizeof(header), err);
if (header_len != 6) {
if (header_len < 0)
err = tr("ERROR: reading header: ") + err;
else
err = tr("ERROR: timeout reading header");
return false;
}
if (PT_DEBUG) {
printf("read header \"%s\"\n",
cEscape((char*) header,
sizeof(header)).toLatin1().constData());
}
QVector<unsigned char> records;
for (size_t i = 0; i < sizeof(header); ++i)
records.append(header[i]);
emit updateStatus( tr("Reading ride data...") );
if(m_Cancelled) {
err = tr("download cancelled");
return false;
}
double recIntSecs = 0.0;
fflush(stdout);
while (true) {
if (PT_DEBUG) printf("reading block\n");
unsigned char buf[256 * 6 + 1];
int n = dev->read(buf, 2, err);
if (n < 2) {
if (n < 0)
err = tr("ERROR: reading first two: ") + err;
else
err = tr("ERROR: timeout reading first two");
return false;
}
if (PT_DEBUG) {
printf("read 2 bytes: \"%s\"\n",
cEscape((char*) buf, 2).toLatin1().constData());
}
if (hasNewline((char*) buf, 2))
break;
unsigned count = 2;
while (count < sizeof(buf)) {
n = dev->read(buf + count, sizeof(buf) - count, err);
if (n < 0) {
err = tr("ERROR: reading block: ") + err;
return false;
}
if (n == 0) {
err = tr("ERROR: timeout reading block");
return false;
}
if (PT_DEBUG) {
printf("read %d bytes: \"%s\"\n", n,
cEscape((char*) buf + count, n).toLatin1().constData());
}
count += n;
}
unsigned csum = 0;
for (int i = 0; i < ((int) sizeof(buf)) - 1; ++i)
csum += buf[i];
if ((csum % 256) != buf[sizeof(buf) - 1]) {
err = tr("ERROR: bad checksum");
return false;
}
if (PT_DEBUG) printf("good checksum\n");
for (size_t i = 0; i < sizeof(buf) - 1; ++i)
records.append(buf[i]);
if (recIntSecs == 0.0) {
unsigned char *data = records.data();
bool bIsVer81 = PowerTapUtil::is_Ver81(data);
for (int i = 0; i < records.size(); i += 6) {
if (PowerTapUtil::is_config(data + i, bIsVer81)) {
unsigned unused1, unused2, unused3;
PowerTapUtil::unpack_config(
data + i, &unused1, &unused2,
&recIntSecs, &unused3, bIsVer81);
}
}
}
if (recIntSecs != 0.0) {
int min = (int) round(records.size() / 6 * recIntSecs);
emit updateProgress( QString(tr("progress: %1:%2"))
.arg(min / 60)
.arg(min % 60, 2, 10, QLatin1Char('0')));
}
if(m_Cancelled){
err = tr("download cancelled");
return false;
}
if (!doWrite(dev, 0x71, hwecho, err)) // 'q'
return false;
}
QString tmpl = tmpdir.absoluteFilePath(".ptdl.XXXXXX");
QTemporaryFile tmp(tmpl);
tmp.setAutoRemove(false);
if (!tmp.open()) {
err = tr("Failed to create temporary file ")
+ tmpl + ": " + tmp.error();
return false;
}
// QTemporaryFile initially has permissions set to 0600.
// Make it readable by everyone.
tmp.setPermissions(tmp.permissions()
| QFile::ReadOwner | QFile::ReadUser
| QFile::ReadGroup | QFile::ReadOther);
DeviceDownloadFile file;
file.extension = "raw";
file.name = tmp.fileName();
QTextStream os(&tmp);
os << hex;
os.setPadChar('0');
bool time_set = false;
unsigned char *data = records.data();
bool bIsVer81 = PowerTapUtil::is_Ver81(data);
for (int i = 0; i < records.size(); i += 6) {
if (data[i] == 0 && !bIsVer81)
continue;
for (int j = 0; j < 6; ++j) {
os.setFieldWidth(2);
os << data[i+j];
os.setFieldWidth(1);
os << ((j == 5) ? "\n" : " ");
}
if (!time_set && PowerTapUtil::is_time(data + i, bIsVer81)) {
struct tm time;
time_t timet = PowerTapUtil::unpack_time(data + i, &time, bIsVer81);
file.startTime.setTime_t( timet );
time_set = true;
}
}
if (!time_set) {
err = tr("Failed to find ride time.");
tmp.setAutoRemove(true);
return false;
}
files << file;
return true;
}