mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 16:39:57 +00:00
... Further enablement for translation ... some typo in EN (seen during translation) (cherry picked from commit 50b78cec0f2fae40f9a5974d98e145d9d9d55535)
409 lines
10 KiB
C++
409 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 (tr("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,
|
|
QString &err)
|
|
{
|
|
if (MACRO_DEBUG) printf("download O-Synce Macro");
|
|
|
|
if (!dev->open(err)) {
|
|
err = tr("ERROR: open failed: ") + err;
|
|
return false;
|
|
}
|
|
|
|
emit updateStatus(tr("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(m_Cancelled)
|
|
{
|
|
err = tr("download cancelled");
|
|
return false;
|
|
}
|
|
|
|
MacroPacket response = MacroPacket();
|
|
response.read(dev, 2, err);
|
|
|
|
if (response.payload.size() == 0)
|
|
{
|
|
err = tr("no data");
|
|
return false;
|
|
}
|
|
|
|
char count = response.payload.at(0);
|
|
|
|
if (count == 0)
|
|
{
|
|
err = tr("no data");
|
|
return false;
|
|
}
|
|
|
|
response.read(dev, 7*count, err);
|
|
|
|
if (!response.verifyCheckSum(dev, err))
|
|
{
|
|
err = tr("data error");
|
|
return false;
|
|
}
|
|
|
|
if(m_Cancelled)
|
|
{
|
|
err = tr("download cancelled");
|
|
return false;
|
|
}
|
|
|
|
// create temporary file
|
|
QString tmpl = tmpdir.absoluteFilePath(".macrodl.XXXXXX");
|
|
QTemporaryFile tmp(tmpl);
|
|
tmp.setAutoRemove(false);
|
|
|
|
if (!tmp.open()) {
|
|
err = tr("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);
|
|
emit updateStatus( QString(tr("Request datas of training %1 / %2..."))
|
|
.arg(i+1).arg((int)count) );
|
|
|
|
if(m_Cancelled)
|
|
{
|
|
err = tr("download cancelled");
|
|
return false;
|
|
}
|
|
|
|
cmd = MacroPacket(TRAINING_DETAIL_REQUEST);
|
|
cmd.addToPayload(i);
|
|
if (!cmd.write(dev, err)) return false;
|
|
|
|
if(m_Cancelled)
|
|
{
|
|
err = tr("download cancelled");
|
|
return false;
|
|
}
|
|
bool lastpage = false;
|
|
while (!lastpage)
|
|
{
|
|
MacroPacket response2 = MacroPacket();
|
|
response2.read(dev, 259, err);
|
|
|
|
if (!response2.verifyCheckSum(dev, err))
|
|
{
|
|
err = tr("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());
|
|
emit updateProgress( QString(tr("training %1/%2... (%3 Bytes)"))
|
|
.arg(i+1)
|
|
.arg((int)count)
|
|
.arg(tmp.size()) );
|
|
if(m_Cancelled)
|
|
{
|
|
err = tr("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 = tr("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) ? (tr("read checksum error: ") + err) : tr("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).toLatin1().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.toLatin1().constData());
|
|
err = QString(tr("failed to write to device: %1")).arg(err);
|
|
}
|
|
else {
|
|
if (MACRO_DEBUG) printf("timeout writing %s to device\n", msg);
|
|
err = QString(tr("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) ? (tr("read command error: ") + err) : tr("read timeout");
|
|
return false;
|
|
}
|
|
checksum += command;
|
|
len--;
|
|
if (MACRO_DEBUG) printf("command %s\n" ,cEscape(&command,n).toLatin1().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) ? (tr("read error: ") + err) : tr("read timeout");
|
|
return false;
|
|
} else if (n < len) {
|
|
err += QString(tr(", read only %1 bytes instead of: %2"))
|
|
.arg(n).arg(len);
|
|
return false;
|
|
}
|
|
|
|
if (MACRO_DEBUG) printf("payload %s\n" ,cEscape(buf,n).toLatin1().constData());
|
|
addToPayload(buf,n);
|
|
|
|
return true;
|
|
}
|