mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 16:39:57 +00:00
The new powercontrols have a lot more memory and they allow you to selectively download the recorded "rides". Looking at srmwin, this seems to be the suggested way of operation. (i.e. record multiple workouts, download only the "new" ones). Furthermore, the SRM file format has some limits (timespan, total number of records), that make it inapropriate to store "all rides" into one file and split it later. So download now - tries to get a list of rides of the device - if it gets any, the user can get prompted to choose which to download. - let device download (selected/all) rides, split if necessary and return a list with tmp filename, start time, file extension. - download dialog builds new filename based on time, prompts user for overwriting when file exists and renames file. The download Dialog now stays open, so user can read the status messages and click "cleanup". This avoids many of the anoying message boxes we had in the Srm download. Cleanup's user interaction (confirmation, errors) was moved from the individual device to DownloadDialog, as well.
405 lines
10 KiB
C++
405 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2011 Damien Grauser (Damien.Grauser@pev-geneve.ch)
|
|
*
|
|
* 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 "MacroDevice.h"
|
|
#include "DownloadRideDialog.h"
|
|
#include "PowerTapUtil.h"
|
|
#include "Device.h"
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
|
|
#define MACRO_DEBUG false
|
|
|
|
#define UNKNOWN 0x00
|
|
|
|
// Commands send from PC to computer
|
|
#define ACKNOWLEDGE 0x0A // Acknowledge received data
|
|
#define NUMBER_OF_TRAINING_REQUESTS 0x06 // Request the number of trainings
|
|
#define TRAINING_DETAIL_REQUEST 0x09 // Request details of a training
|
|
#define ERASE_ALL_RECORDS 0x0B // Erase all records on computer
|
|
|
|
// Commands send from computer to PC
|
|
#define NUMBER_OF_TRAININGS 0x86 // Packet with the trainings count
|
|
#define TRAINING_DETAIL 0x89 // Packet with details about a specific training
|
|
#define ERASE_DONE 0x8B // Packet which acknowledges that all data is erased in computer
|
|
|
|
#define LAST_PAGE 0xFA0A // LastPage number
|
|
|
|
static bool macroRegistered =
|
|
Devices::addType("O-Synce Macro PC-Link", DevicesPtr(new MacroDevices()) );
|
|
|
|
QString
|
|
MacroDevices::downloadInstructions() const
|
|
{
|
|
return ("Make sure the Macro unit is turned\n"
|
|
"on and that its display says, \"PC Link\"");
|
|
}
|
|
|
|
DevicePtr
|
|
MacroDevices::newDevice( CommPortPtr dev )
|
|
{
|
|
return DevicePtr( new MacroDevice( dev ));
|
|
}
|
|
|
|
static QString
|
|
cEscape(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;
|
|
}
|
|
|
|
static int
|
|
bcd2Int(char c)
|
|
{
|
|
return (0xff & c) - (((0xff & c)/16)*6);
|
|
}
|
|
|
|
static int
|
|
hex2Int(char c)
|
|
{
|
|
return (0xff & c);
|
|
}
|
|
|
|
static int
|
|
hexHex2Int(char c, char c2)
|
|
{
|
|
return (0xff & c) + 256*(0xff & c2);
|
|
}
|
|
|
|
bool
|
|
MacroDevice::download( const QDir &tmpdir,
|
|
QList<DeviceDownloadFile> &files,
|
|
CancelCallback cancelCallback,
|
|
StatusCallback statusCallback,
|
|
ProgressCallback progressCallback,
|
|
QString &err)
|
|
{
|
|
if (MACRO_DEBUG) printf("download O-Synce Macro");
|
|
|
|
if (!dev->open(err)) {
|
|
err = "ERROR: open failed: " + err;
|
|
return false;
|
|
}
|
|
|
|
statusCallback("Request number of training...");
|
|
if (MACRO_DEBUG) printf("Request number of training\n");
|
|
|
|
MacroPacket cmd(NUMBER_OF_TRAINING_REQUESTS);
|
|
cmd.addToPayload(UNKNOWN);
|
|
|
|
if (!cmd.write(dev, err)) return false;
|
|
|
|
if (cancelCallback())
|
|
{
|
|
err = "download cancelled";
|
|
return false;
|
|
}
|
|
|
|
MacroPacket response = MacroPacket();
|
|
response.read(dev, 2, err);
|
|
char count = response.payload.at(0);
|
|
|
|
if (count == 0)
|
|
{
|
|
err = "no data";
|
|
return false;
|
|
}
|
|
|
|
response.read(dev, 7*count, err);
|
|
|
|
if (!response.verifyCheckSum(dev, err))
|
|
{
|
|
err = "data error";
|
|
return false;
|
|
}
|
|
|
|
if (cancelCallback())
|
|
{
|
|
err = "download cancelled";
|
|
return false;
|
|
}
|
|
|
|
// create temporary file
|
|
QString tmpl = tmpdir.absoluteFilePath(".macrodl.XXXXXX");
|
|
QTemporaryFile tmp(tmpl);
|
|
tmp.setAutoRemove(false);
|
|
|
|
if (!tmp.open()) {
|
|
err = "Failed to create temporary file "
|
|
+ tmpl + ": " + tmp.error();
|
|
return false;
|
|
}
|
|
|
|
if (MACRO_DEBUG) printf("Acknowledge");
|
|
cmd= MacroPacket(ACKNOWLEDGE);
|
|
cmd.addToPayload(response.command);
|
|
if (!cmd.write(dev, err)) return false;
|
|
|
|
// timestamp from the first training
|
|
struct tm start;
|
|
start.tm_sec = bcd2Int(response.payload.at(2));
|
|
start.tm_min = bcd2Int(response.payload.at(3));
|
|
start.tm_hour = bcd2Int(response.payload.at(4));
|
|
start.tm_mday = bcd2Int(response.payload.at(5));
|
|
start.tm_mon = hex2Int(response.payload.at(6)) -1;
|
|
start.tm_year = bcd2Int(response.payload.at(7)) -100;
|
|
start.tm_isdst = -1;
|
|
|
|
DeviceDownloadFile file;
|
|
file.extension = "osyn";
|
|
file.name = tmp.fileName();
|
|
file.startTime.setTime_t( mktime( &start ));
|
|
files.append(file);
|
|
|
|
QTextStream os(&tmp);
|
|
os << hex;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (MACRO_DEBUG) printf("Request training %d\n",i);
|
|
statusCallback( QString("Request datas of training %1 / %2...")
|
|
.arg(i+1).arg((int)count) );
|
|
|
|
if (cancelCallback())
|
|
{
|
|
err = "download cancelled";
|
|
return false;
|
|
}
|
|
|
|
cmd = MacroPacket(TRAINING_DETAIL_REQUEST);
|
|
cmd.addToPayload(i);
|
|
if (!cmd.write(dev, err)) return false;
|
|
|
|
if (cancelCallback())
|
|
{
|
|
err = "download cancelled";
|
|
return false;
|
|
}
|
|
bool lastpage = false;
|
|
while (!lastpage)
|
|
{
|
|
MacroPacket response2 = MacroPacket();
|
|
response2.read(dev, 259, err);
|
|
|
|
if (!response2.verifyCheckSum(dev, err))
|
|
{
|
|
err = "data error";
|
|
return false;
|
|
}
|
|
|
|
if (hexHex2Int(response2.payload.at(0), response2.payload.at(1)) == LAST_PAGE)
|
|
lastpage = true;
|
|
|
|
//int training_flag = hex2Int(response2.payload.at(43));
|
|
tmp.write(response2.dataArray());
|
|
progressCallback( QString("training %1/%2... (%3 Bytes)")
|
|
.arg(i+1)
|
|
.arg((int)count)
|
|
.arg(tmp.size()) );
|
|
if (cancelCallback())
|
|
{
|
|
err = "download cancelled";
|
|
return false;
|
|
}
|
|
|
|
if (MACRO_DEBUG) printf("Acknowledge\n");
|
|
|
|
cmd= MacroPacket(ACKNOWLEDGE);
|
|
cmd.addToPayload(response2.command);
|
|
if (!cmd.write(dev, err)) return false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
tmp.close();
|
|
|
|
dev->close();
|
|
|
|
// QTemporaryFile initially has permissions set to 0600.
|
|
// Make it readable by everyone.
|
|
tmp.setPermissions(tmp.permissions()
|
|
| QFile::ReadOwner | QFile::ReadUser
|
|
| QFile::ReadGroup | QFile::ReadOther);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
MacroDevice::cleanup( QString &err ){
|
|
if (MACRO_DEBUG) printf("Erase all records on computer\n");
|
|
|
|
if (!dev->open(err)) {
|
|
err = "ERROR: open failed: " + err;
|
|
}
|
|
|
|
MacroPacket cmd(ERASE_ALL_RECORDS);
|
|
cmd.addToPayload(UNKNOWN);
|
|
|
|
if (!cmd.write(dev, err)) if (MACRO_DEBUG) printf("Error during erase all records on computer\n");
|
|
|
|
MacroPacket response = MacroPacket();
|
|
response.read(dev, 3, err);
|
|
|
|
if (!response.verifyCheckSum(dev, err) || (response.payload.at(0) != (char)ERASE_DONE))
|
|
{
|
|
if (MACRO_DEBUG) printf("Error during erase all records on computer\n");
|
|
}
|
|
|
|
dev->close();
|
|
|
|
return true;
|
|
}
|
|
|
|
// --------------------------------------
|
|
// MacroPacket
|
|
// --------------------------------------
|
|
|
|
MacroPacket::MacroPacket() : command(0)
|
|
{
|
|
checksum = 0;
|
|
payload.clear();
|
|
}
|
|
|
|
MacroPacket::MacroPacket(char command) : command(command), checksum(command)
|
|
{
|
|
payload.clear();
|
|
}
|
|
|
|
void
|
|
MacroPacket::addToPayload(char byte)
|
|
{
|
|
checksum += byte;
|
|
payload.append(byte);
|
|
}
|
|
|
|
void
|
|
MacroPacket::addToPayload(char *bytes, int len)
|
|
{
|
|
for (int i = 0; i < len; ++i) {
|
|
char byte = bytes[i];
|
|
checksum += byte;
|
|
payload.append(byte);
|
|
}
|
|
}
|
|
|
|
bool
|
|
MacroPacket::verifyCheckSum(CommPortPtr dev, QString &err)
|
|
{
|
|
char _checksum;
|
|
if (MACRO_DEBUG) printf("reading checksum from device\n");
|
|
int n = dev->read(&_checksum, 1, err);
|
|
if (n <= 0) {
|
|
err = (n < 0) ? ("read checksum error: " + err) : "read timeout";
|
|
return false;
|
|
}
|
|
if (MACRO_DEBUG) printf("CheckSum1 %d CheckSum2 %d", (0xff & (unsigned) checksum) , (0xff & (unsigned) _checksum));
|
|
|
|
return checksum == _checksum;
|
|
}
|
|
|
|
QByteArray
|
|
MacroPacket::dataArray()
|
|
{
|
|
QByteArray result;
|
|
result.append(command);
|
|
result.append(payload);
|
|
result.append(checksum);
|
|
|
|
return result;
|
|
}
|
|
|
|
char*
|
|
MacroPacket::data()
|
|
{
|
|
QByteArray result;
|
|
result.append(command);
|
|
result.append(payload);
|
|
result.append(checksum);
|
|
|
|
return result.data();
|
|
}
|
|
|
|
bool
|
|
MacroPacket::write(CommPortPtr dev, QString &err)
|
|
{
|
|
const char *msg = cEscape(data(), payload.count()+2).toAscii().constData();
|
|
|
|
if (MACRO_DEBUG) printf("writing '%s' to device\n", msg);
|
|
|
|
int n = dev->write(data(), payload.count()+2, err);
|
|
if (n != payload.count()+2) {
|
|
if (n < 0) {
|
|
if (MACRO_DEBUG) printf("failed to write %s to device: %s\n", msg, err.toAscii().constData());
|
|
err = QString("failed to write to device: %1").arg(err);
|
|
}
|
|
else {
|
|
if (MACRO_DEBUG) printf("timeout writing %s to device\n", msg);
|
|
err = QString("timeout writing to device");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (MACRO_DEBUG) printf("writing to device ok\n");
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
MacroPacket::read(CommPortPtr dev, int len, QString &err)
|
|
{
|
|
if (command == 0) {
|
|
if (MACRO_DEBUG) printf("reading command from device\n");
|
|
int n = dev->read(&command, 1, err);
|
|
if (n <= 0) {
|
|
err = (n < 0) ? ("read command error: " + err) : "read timeout";
|
|
return false;
|
|
}
|
|
checksum += command;
|
|
len--;
|
|
if (MACRO_DEBUG) printf("command %s\n" ,cEscape(&command,n).toAscii().constData());
|
|
}
|
|
|
|
if (MACRO_DEBUG) printf("reading %d from device\n", len);
|
|
char buf[len];
|
|
int n = dev->read(&buf, len, err);
|
|
|
|
if (n <= 0) {
|
|
err = (n < 0) ? ("read error: " + err) : "read timeout";
|
|
return false;
|
|
} else if (n < len) {
|
|
err += QString(", read only %1 bytes insteed of: %2")
|
|
.arg(n).arg(len);
|
|
return false;
|
|
}
|
|
|
|
if (MACRO_DEBUG) printf("payload %s\n" ,cEscape(buf,n).toAscii().constData());
|
|
addToPayload(buf,n);
|
|
|
|
return true;
|
|
}
|