Joule GPS Support

This is BETA support for downloading ride files
from the new Cyclops Powertap Joule GPS.
This commit is contained in:
Damien Grauser
2012-11-05 21:33:05 +00:00
committed by Mark Liversedge
parent 07c2bafb08
commit 6e6b36f2af
12 changed files with 1142 additions and 3 deletions

417
src/Bin2RideFile.cpp Normal file
View File

@@ -0,0 +1,417 @@
/*
* Copyright (c) 2012 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 "Bin2RideFile.h"
#include <math.h>
#define START 0x210
#define UNIT_VERSION 0x2000
#define SYSTEM_INFO 0x2003
static int bin2FileReaderRegistered =
RideFileFactory::instance().registerReader(
"bin2", "Joule GPS File", new Bin2FileReader());
struct Bin2FileReaderState
{
QFile &file;
QStringList &errors;
RideFile *rideFile;
double secs, km;
int interval;
double last_interval_secs;
bool stopped;
QString deviceInfo;
Bin2FileReaderState(QFile &file, QStringList &errors) :
file(file), errors(errors), rideFile(NULL), secs(0), km(0),
interval(0), last_interval_secs(0.0), stopped(true)
{
}
struct TruncatedRead {};
int bcd2Int(char c)
{
return (0xff & c) - (((0xff & c)/16)*6);
}
int read_bytes(int len, int *count = NULL, int *sum = NULL)
{
char c;
int res = 0;
for (int i = 0; i < len; ++i) {
if (file.read(&c, 1) != 1)
throw TruncatedRead();
if (sum)
*sum += (0xff & c);
if (count)
*count += 1;
res += pow(256,i) * (0xff & (unsigned) c) ;
}
return res;
}
QString read_text(int len, int *count = NULL, int *sum = NULL)
{
char c;
QString res = "";
for (int i = 0; i < len; ++i) {
if (file.read(&c, 1) != 1)
throw TruncatedRead();
if (sum)
*sum += (0xff & c);
if (count)
*count += 1;
res += c;
}
return res;
}
QDateTime read_date(double *secs, int *bytes_read = NULL, int *sum = NULL)
{
int sec = bcd2Int(read_bytes(1, bytes_read, sum));
int min = bcd2Int(read_bytes(1, bytes_read, sum));
int hour = bcd2Int(read_bytes(1, bytes_read, sum));
int day = bcd2Int(read_bytes(1, bytes_read, sum));
int month = bcd2Int(read_bytes(1, bytes_read, sum));
int year = bcd2Int(read_bytes(1, bytes_read, sum));
return QDateTime(QDate(2000+year,month,day), QTime(hour,min,sec));
}
QDateTime read_RTC_mark(double *secs, int *bytes_read = NULL, int *sum = NULL)
{
QDateTime date = read_date(secs, bytes_read, sum);
int dummy1 = read_bytes(1, bytes_read, sum);
int time_moving = read_bytes(4, bytes_read, sum);
int secs2 = read_bytes(4, bytes_read, sum);
int dummy2 = read_bytes(16, bytes_read, sum);
return date;
}
void read_detail_record(double *secs, int *bytes_read = NULL, int *sum = NULL)
{
int cad = read_bytes(1, bytes_read, sum);
int pedal_smoothness = read_bytes(1, bytes_read, sum);
int lrbal = read_bytes(1, bytes_read, sum);
int hr = read_bytes(1, bytes_read, sum);
int dummy = read_bytes(1, bytes_read, sum);
int watts = read_bytes(2, bytes_read, sum);
int nm = read_bytes(2, bytes_read, sum);
double kph = read_bytes(2, bytes_read, sum);
int alt = read_bytes(2, bytes_read, sum);
double temp = read_bytes(2, bytes_read, sum)/10.0;
double lat = read_bytes(4, bytes_read, sum);
double lng = read_bytes(4, bytes_read, sum);
double km = read_bytes(8, bytes_read, sum)/1000.0/1000.0;
// Validations
if (lrbal == 0xFF)
lrbal = 0;
else if ((lrbal & 0x200) == 0x200)
lrbal = 100-lrbal;
if (cad == 0xFF)
cad = 0;
if (hr == 0xFF)
hr = 0;
if (watts == 0xFFFF) // 65535
watts = 0;
if (kph == 0xFFFF) // 65535
kph = 0;
else
kph = kph/10.0;
if (temp == 0x8000)
temp = 0;
if (alt == 0x8000)
alt = 0;
if (lat == -2147483648) //2147483648
lat = 0;
else
lat = lat/10000000.0;
if (lng == -2147483648) //0x80000000
lng = 0;
else
lng = lng/10000000.0;
rideFile->appendPoint(*secs, cad, hr, km, kph, nm, watts, alt, lng, lat, 0.0, 0, temp, lrbal, interval);
(*secs)++;
}
void read_header(uint16_t &header, uint16_t &command, u_int16_t &length, int *bytes_read = NULL, int *sum = NULL)
{
header = read_bytes(2, bytes_read, sum);
command = read_bytes(2, bytes_read, sum);
length = read_bytes(2, bytes_read, sum);
}
void read_ride_summary(int *bytes_read = NULL, int *sum = NULL)
{
char data_version = read_bytes(1, bytes_read, sum);
char firmware_minor_version = read_bytes(1, bytes_read, sum);
double *sec;
QDateTime t = read_date(sec, bytes_read, sum);
rideFile->setStartTime(t);
read_bytes(148, bytes_read, sum);
}
void read_interval_summary(int *bytes_read = NULL, int *sum = NULL)
{
read_bytes(3200, bytes_read, sum);
}
void read_username(int *bytes_read = NULL, int *sum = NULL)
{
QString name = read_text(20, bytes_read, sum);
//deviceInfo += QString("User : %1\n").arg(name);
}
void read_user_info_record(int *bytes_read = NULL, int *sum = NULL)
{
read_bytes(64, bytes_read, sum);
int smartbelt_A = read_bytes(2, bytes_read, sum);
int smartbelt_B = read_bytes(2, bytes_read, sum);
int smartbelt_C = read_bytes(2, bytes_read, sum);
//deviceInfo += QString("Smartbelt %1-%2-%3\n").arg(smartbelt_A).arg(smartbelt_B).arg(smartbelt_C);
read_bytes(42, bytes_read, sum);
}
void read_ant_info_record(int *bytes_read = NULL, int *sum = NULL)
{
for (int i = 0; i < 6; i++) {
int device_type = read_bytes(1, bytes_read, sum);
if (device_type < 255) {
QString text = read_text(20, bytes_read, sum);
while(text.endsWith( QChar(0) )) text.chop(1);
QChar *chr = text.end();
int i = chr->toAscii();
int flag = read_bytes(1, bytes_read, sum);
u_int16_t id = read_bytes(2, bytes_read, sum);
read_bytes(2, bytes_read, sum);
read_bytes(2, bytes_read, sum);
QString device_type_str;
if (device_type == 11)
device_type_str = "Primary Power Id";
else if (device_type == 120)
device_type_str = "Chest strap Id";
else
device_type_str = QString("ANT %1 Id").arg(device_type);
deviceInfo += QString("%1 %2 %3\n").arg(device_type_str).arg(id).arg(text);
}
}
}
// pages
int read_summary_page()
{
int sum = 0;
int bytes_read = 0;
char header1 = read_bytes(1, &bytes_read, &sum); // Always 0x10
char header2 = read_bytes(1, &bytes_read, &sum); // Always 0x02
uint16_t command = read_bytes(2, &bytes_read, &sum); // Always 0x10
if (header1 == 0x10 && header2 == 0x02 && command == 0x2022)
{
u_int16_t length = read_bytes(2, &bytes_read, &sum); // Always 4098 for Joule GPS
u_int16_t page_number = read_bytes(2, &bytes_read, &sum); // Page #
if (page_number == 0) {
// Page #0
read_ride_summary(&bytes_read, &sum);
read_interval_summary(&bytes_read, &sum);
read_username(&bytes_read, &sum);
read_user_info_record(&bytes_read, &sum);
read_ant_info_record(&bytes_read, &sum);
int finish = length+6-bytes_read;
for (int i = 0; i < finish; i++) {
read_bytes(1, &bytes_read, &sum); // to finish
}
char checksum = read_bytes(1, &bytes_read, &sum); // Always 0x10
} else {
// not a summary page !
}
}
return bytes_read;
}
int read_detail_page()
{
int sum = 0;
int bytes_read = 0;
char header1 = read_bytes(1, &bytes_read, &sum); // Always 0x10
char header2 = read_bytes(1, &bytes_read, &sum); // Always 0x02
uint16_t command = read_bytes(2, &bytes_read, &sum); // Always 0x10
if (header1 == 0x10 && header2 == 0x02 && command == 0x2022)
{
u_int16_t length = read_bytes(2, &bytes_read, &sum); // Always 4098
u_int16_t page_number = read_bytes(2, &bytes_read, &sum); // Page #
if (page_number > 0) {
// Page # >0
// 128 x 32k
QDateTime t;
for (int i = 0; i < 128; i++) {
int flag = read_bytes(1, &bytes_read, &sum);
if ((flag & 0x03) == 0x01 || (flag & 0x03) == 0x03){
t= read_RTC_mark(&secs, &bytes_read, &sum);
if ((flag & 0x03) == 0x03)
interval++;
}
else if ((flag & 0x03) == 0x00 ){
read_detail_record(&secs, &bytes_read, &sum);
}
}
char checksum = read_bytes(1, &bytes_read, &sum); // Always 0x10
}
}
return bytes_read;
}
int read_version()
{
int sum = 0;
int bytes_read = 0;
uint16_t header = read_bytes(2, &bytes_read, &sum); // Always 0x210 (0x10-0x02)
uint16_t command = read_bytes(2, &bytes_read, &sum); // Always 0x10
if (header == START && command == UNIT_VERSION)
{
u_int16_t length = read_bytes(2, &bytes_read, &sum); // Always 4098 for Joule GPS
int major_version = read_bytes(1, &bytes_read, &sum);
int minor_version = read_bytes(2, &bytes_read, &sum);
int data_version = read_bytes(2, &bytes_read, &sum);
QString version = QString(minor_version<100?"%1.0%2 (%3)":"%1.%2 (%3)").arg(major_version).arg(minor_version).arg(data_version);
deviceInfo += rideFile->deviceType()+QString(" Version %1\n").arg(version);
char checksum = read_bytes(1, &bytes_read, &sum); // Always 4098 for Joule GPS
}
return bytes_read;
}
int read_system_info()
{
int sum = 0;
int bytes_read = 0;
uint16_t header = read_bytes(2, &bytes_read, &sum); // Always (0x10-0x02)
uint16_t command = read_bytes(2, &bytes_read, &sum); // Always 0x10
if (header == START && command == SYSTEM_INFO)
{
u_int16_t length = read_bytes(2, &bytes_read, &sum); // Always 4098 for Joule GPS
read_bytes(52, &bytes_read, &sum);
u_int16_t odometer = read_bytes(8, &bytes_read, &sum); // Always 4098 for Joule GPS
deviceInfo += QString("Odometer %1km\n").arg(odometer/1000.0);
char checksum = read_bytes(1, &bytes_read, &sum); // Always 4098 for Joule GPS
}
return bytes_read;
}
RideFile * run() {
errors.clear();
rideFile = new RideFile;
rideFile->setDeviceType("Joule GPS");
rideFile->setRecIntSecs(1);
if (!file.open(QIODevice::ReadOnly)) {
delete rideFile;
return NULL;
}
bool stop = false;
int data_size = file.size();
int bytes_read = 0;
bytes_read += read_version();
bytes_read += read_system_info();
bytes_read += read_summary_page();
while (!stop && (bytes_read < data_size)) {
bytes_read += read_detail_page(); // read_page(stop, errors);
}
rideFile->setTag("Device Info", deviceInfo);
if (stop) {
delete rideFile;
return NULL;
}
else {
return rideFile;
}
}
};
RideFile *Bin2FileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
QSharedPointer<Bin2FileReaderState> state(new Bin2FileReaderState(file, errors));
return state->run();
}

30
src/Bin2RideFile.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2012 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
*/
#ifndef _Bin2RideFile_h
#define _Bin2RideFile_h
#include "GoldenCheetah.h"
#include "RideFile.h"
struct Bin2FileReader : public RideFileReader {
virtual RideFile *openRideFile(QFile &file, QStringList &errors, QList<RideFile*>* = 0) const;
bool hasWrite() const { return false; }
};
#endif // _Bin2RideFile_h

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2007 Sean C. Rhea (srhea@srhea.net)
* Copyright (c) 2012 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

View File

@@ -42,6 +42,8 @@ class CommPort
virtual int read(void *buf, size_t nbyte, QString &err) = 0;
virtual int write(void *buf, size_t nbyte, QString &err) = 0;
virtual QString name() const = 0;
virtual bool setBaudRate(int speed, QString &err) = 0;
QString type( void ) const;
QString id( void ) const;

View File

@@ -195,6 +195,19 @@ D2XX::name() const
return info.Description;
}
bool
D2XX::setBaudRate(int speed, QString &err)
{
assert(_isOpen);
FT_STATUS ftStatus = lib->set_baud_rate(ftHandle, speed);
if (ftStatus != FT_OK) {
err = QString("FT_SetBaudRate: %1").arg(ftStatus);
return false;
}
return true;
}
QVector<CommPortPtr>
D2XX::myListCommPorts(QString &err)
{

View File

@@ -47,6 +47,7 @@ class D2XX : public CommPort
virtual int read(void *buf, size_t nbyte, QString &err);
virtual int write(void *buf, size_t nbyte, QString &err);
virtual QString name() const;
virtual bool setBaudRate(int speed, QString &err);
};
#endif // _GC_PT_D2XX_h

View File

@@ -30,6 +30,12 @@ struct DeviceDownloadFile
QString extension;
};
struct DeviceStoredRideItem
{
int id;
QDateTime startTime;
};
struct DeviceRideItem
{
bool wanted;

590
src/JouleDevice.cpp Normal file
View File

@@ -0,0 +1,590 @@
/*
* Copyright (c) 2012 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 "JouleDevice.h"
#include "DownloadRideDialog.h"
#include "PowerTapUtil.h"
#include "Device.h"
#include <math.h>
#include <errno.h>
#include <termios.h>
#define JOULE_DEBUG false // debug traces
// Start pattern
#define START_1 0x10
#define START_2 0x02
// Commands send from PC to Joule
#define READ_UNIT_VERSION 0x2000
#define GET_FREE_SPACE 0x2002
#define READ_SYSTEM_INFO 0x2003
#define READ_RIDE_SUMMARY 0x2020
#define READ_RIDE_DETAIL 0x2021
#define PAGE_RIDE_DETAIL 0x2022
#define ERASE_RIDE_DETAIL 0x2024
static bool jouleRegistered =
Devices::addType("Joule GPS (BETA)", DevicesPtr(new JouleDevices()) );
QString
JouleDevices::downloadInstructions() const
{
return ("Make sure the Joule GPS unit is turned ON\n");
}
DevicePtr
JouleDevices::newDevice( CommPortPtr dev, Device::StatusCallback cb )
{
return DevicePtr( new JouleDevice( dev, cb ));
}
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
qByteArray2Int(QByteArray array)
{
int result = 0;
for (int i = 0; i < array.size(); i++)
{
result += pow(256,i)*(0xff & array.at(i));
}
return result;
}
static int
readOneByOne(CommPortPtr dev, void *buf, size_t nbyte, QString &err)
{
char * data = ((char *)buf);
int rtn=0;
for (int i = 0; i < nbyte; i++)
{
int n = dev->read(data + i, 1, err);
if (n <= 0) {
return rtn;
}
if (data[i] == START_1){
int n = dev->read(data + i, 1, err);
if (n <= 0) {
return rtn;
}
}
rtn++;
}
return rtn;
}
bool
JouleDevice::download( const QDir &tmpdir,
QList<DeviceDownloadFile> &files,
CancelCallback cancelCallback,
ProgressCallback progressCallback,
QString &err)
{
if (JOULE_DEBUG) printf("download Joule GPS");
if (!dev->open(err)) {
err = "ERROR: open failed: " + err;
return false;
}
dev->setBaudRate(57600, err);
QString version = QString("");
QString serial = QString("");
QByteArray versionArray;
QByteArray systemInfoArray;
getUnitVersion(version, versionArray, err);
statusCallback("Version"+version);
getSystemInfo(serial, systemInfoArray, err);
QList<DeviceStoredRideItem> trainings;
getDownloadableRides(trainings, err);
for (int i=0; i<trainings.count(); i++) {
statusCallback(QString("Read ride detail for ride %1/%2").arg(i+1).arg(trainings.count()));
JoulePacket request(READ_RIDE_DETAIL);
int id1 = (trainings.at(i).id>255?trainings.at(i).id-255:trainings.at(i).id);
int id2 = (trainings.at(i).id>255?trainings.at(i).id%255:0);
request.addToPayload((char)id1);
request.addToPayload((char)id2);
request.addToPayload((uint16_t)0xFFFF); // Total Ride#
request.addToPayload((uint16_t)0x0000); // Start Page#
if (!request.write(dev, err)) return false;
if (cancelCallback())
{
err = "download cancelled";
return false;
}
JoulePacket response = JoulePacket(PAGE_RIDE_DETAIL);
if (response.read(dev, err)) {
if (response.payload.size() < 4096)
{
err = "no data";
return false;
}
int page = qByteArray2Int(response.payload.left(2));
int data_version = qByteArray2Int(response.payload.mid(2,1));
int firmware = qByteArray2Int(response.payload.mid(3,1));
QString data = QString("%1 %2-%3").arg(page).arg(data_version).arg(firmware);
qDebug() << data;
if (page < 65535) {
// create temporary file
QString tmpl = tmpdir.absoluteFilePath(".joule.XXXXXX"); //".joulegps.XXXXXX"
QTemporaryFile tmp(tmpl);
tmp.setAutoRemove(false);
if (!tmp.open()) {
err = "Failed to create temporary file "
+ tmpl + ": " + tmp.error();
return false;
}
// timestamp from the first training
struct tm start;
start.tm_sec = bcd2Int(response.payload.at(4));
start.tm_min = bcd2Int(response.payload.at(5));
start.tm_hour = bcd2Int(response.payload.at(6));
start.tm_mday = bcd2Int(response.payload.at(7));
start.tm_mon = bcd2Int(response.payload.at(8))-1;
start.tm_year = 2000 + bcd2Int(response.payload.at(9))-1900;
start.tm_isdst = -1;
DeviceDownloadFile file;
file.extension = "bin2";
file.name = tmp.fileName();
file.startTime.setTime_t( mktime( &start ));
files.append(file);
QTextStream os(&tmp);
os << hex;
qDebug() << tmp.fileName() << "-" << tmpl;
tmp.write(versionArray);
tmp.write(systemInfoArray);
tmp.write(response.dataArray());
int _try = 1;
while (page < 65535) {
qDebug() << "page"<<page;
if (JOULE_DEBUG) printf("Acknowledge\n");
request = JoulePacket(PAGE_RIDE_DETAIL);
request.addToPayload(response.payload.left(2).data(),2);
if (!request.write(dev, err))
return false;
response = JoulePacket(PAGE_RIDE_DETAIL);
bool success = false;
while (!success) {
if (response.read(dev, err)) {
page = qByteArray2Int(response.payload.left(2));
tmp.write(response.dataArray());
success = true;
} else {
if (_try == 3)
return false;
else {
JoulePacket request(READ_RIDE_DETAIL);
//request.addToPayload((uint16_t)0x0200); // Ride#
request.addToPayload((char)i);
request.addToPayload((char)0x00);
request.addToPayload((uint16_t)0xFFFF); // Total Ride#
request.addToPayload((uint16_t)page); // Start Page#
if (!request.write(dev, err)) return false;
response = JoulePacket(PAGE_RIDE_DETAIL);
_try ++;
}
}
}
}
tmp.close();
}
qDebug() << "end page" << page;
if (JOULE_DEBUG) printf("Acknowledge\n");
request = JoulePacket(PAGE_RIDE_DETAIL);
request.addToPayload(response.payload.left(2).data(),2);
if (!request.write(dev, err)) return false;
} else
i=99;
}
dev->close();
return true;
}
bool
JouleDevice::getUnitVersion(QString &version, QByteArray &array, QString &err)
{
statusCallback("Get Unit Software Version...");
if (JOULE_DEBUG) printf("Get Unit Software Version\n");
JoulePacket request(READ_UNIT_VERSION);
if (!request.write(dev, err)) return false;
JoulePacket response = JoulePacket(READ_UNIT_VERSION);
if (response.read(dev, err)) {
if (response.payload.length()>4) {
array = response.dataArray();
int major_version = qByteArray2Int(response.payload.left(1));
int minor_version = qByteArray2Int(response.payload.mid(1,2));
int data_version = qByteArray2Int(response.payload.right(2));
version = QString(minor_version<100?"%1.0%2 (%3)":"%1.%2 (%3)").arg(major_version).arg(minor_version).arg(data_version);
}
}
}
bool
JouleDevice::getUnitFreeSpace(QString &memory, QString &err)
{
statusCallback("Get Unit Free Space...");
if (JOULE_DEBUG) printf("Get Unit Free Space\n");
JoulePacket request1(GET_FREE_SPACE);
if (!request1.write(dev, err)) return false;
JoulePacket response1 = JoulePacket(GET_FREE_SPACE);
if (response1.read(dev, err)) {
if (response1.payload.length()>3) {
int empty = qByteArray2Int(response1.payload.left(2));
int total = qByteArray2Int(response1.payload.right(2));
int percentage = 100 * empty / total;
memory = QString("%1/%2 (%3%)").arg(empty).arg(total).arg(percentage);
}
}
}
bool
JouleDevice::getSystemInfo(QString &system, QByteArray &array, QString &err)
{
statusCallback("Get System info...");
if (JOULE_DEBUG) printf("Get System info\n");
JoulePacket request(READ_SYSTEM_INFO);
if (!request.write(dev, err)) return false;
JoulePacket response = JoulePacket(READ_SYSTEM_INFO);
if (response.read(dev, err)) {
if (response.payload.length()>3) {
array = response.dataArray();
int serial = qByteArray2Int(response.payload.left(4));
system = QString("%1").arg(serial);
}
}
}
bool
JouleDevice::getDownloadableRides(QList<DeviceStoredRideItem> &rides, QString &err)
{
statusCallback("Read ride summary...");
if (JOULE_DEBUG) printf("Get Unit Software Version\n");
JoulePacket request(READ_RIDE_SUMMARY);
if (!request.write(dev, err)) return false;
JoulePacket response = JoulePacket(READ_RIDE_SUMMARY);
if (response.read(dev, err)) {
int count = response.payload.length()/20;
for (int i=0; i<count; i++) {
int j = i*20;
int sec = bcd2Int(response.payload.at(j));
int min = bcd2Int(response.payload.at(j+1));
int hour = bcd2Int(response.payload.at(j+2));
int day = bcd2Int(response.payload.at(j+3));
int month = bcd2Int(response.payload.at(j+4))-1;
int year = 2000 + bcd2Int(response.payload.at(j+5));
QDateTime date = QDateTime(QDate(year,month,day), QTime(hour,min,sec));
int total = qByteArray2Int(response.payload.mid(j+18,2));
if (total > 0) {
DeviceStoredRideItem ride;
ride.id = i;
ride.startTime = date;
rides.append(ride);
}
}
}
}
bool
JouleDevice::cleanup( QString &err ) {
if (JOULE_DEBUG) printf("Erase all records on computer\n");
if (!dev->open(err)) {
err = "ERROR: open failed: " + err;
}
QList<DeviceStoredRideItem> trainings;
getDownloadableRides(trainings, err);
for (int i=0; i<trainings.count(); i++) {
statusCallback(QString("Delete ride detail for ride %1/%2").arg(i+1).arg(trainings.count()));
JoulePacket request(ERASE_RIDE_DETAIL);
int id1 = (trainings.at(i).id>255?trainings.at(i).id-255:trainings.at(i).id);
int id2 = (trainings.at(i).id>255?trainings.at(i).id%255:0);
request.addToPayload((char)id1);
request.addToPayload((char)id2);
request.addToPayload((char)id1);
request.addToPayload((char)id2);
if (!request.write(dev, err)) return false;
JoulePacket response = JoulePacket(READ_SYSTEM_INFO);
if (!response.read(dev, err))
return false;
}
dev->close();
return true;
}
// --------------------------------------
// JoulePacket
// --------------------------------------
JoulePacket::JoulePacket() : command(0)
{
checksum = 0;
payload.clear();
}
JoulePacket::JoulePacket(uint16_t command) : command(command), checksum(command)
{
//checksum = 0;
checksum += (uint8_t)((command & 0xFF00) >> 8);
//checksum += (uint8_t)(command & 0x00FF);
payload.clear();
}
void
JoulePacket::addToPayload(char byte)
{
checksum += byte;
payload.append(byte);
}
void
JoulePacket::addToPayload(uint16_t bytes)
{
payload.append(bytes >> 8);
payload.append(bytes & 0x00FF);
checksum += (uint8_t)(bytes >> 8);
checksum += (uint8_t)(bytes & 0x00FF);
}
void
JoulePacket::addToPayload(char *bytes, int len)
{
for (int i = 0; i < len; ++i) {
char byte = bytes[i];
checksum += byte;
payload.append(byte);
}
}
bool
JoulePacket::verifyCheckSum(CommPortPtr dev, QString &err)
{
char _checksum;
if (JOULE_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 (JOULE_DEBUG) printf("CheckSum1 %d CheckSum2 %d", (0xff & (unsigned) checksum) , (0xff & (unsigned) _checksum));
return checksum == _checksum;
}
QByteArray
JoulePacket::dataArray()
{
QByteArray result;
result.append(char(START_1));
result.append(char(START_2));
result.append((char)(command & 0x00FF));
result.append((char)((command & 0xFF00) >> 8));
char lenght1 = (char)(payload.length() & 0x00FF);
char lenght2 = (char)((payload.length() & 0xFF00) >> 8);
result.append(lenght1);
result.append(lenght2);
result.append(payload);
result.append(checksum+lenght1+lenght2);
return result;
}
QByteArray
JoulePacket::dataArrayForUnit()
{
QByteArray result = dataArray();
for (int i = 2; i < result.size(); i++)
{
if (result.at(i) == START_1) {
result.insert(i, START_1);
i++;
}
}
return result;
}
bool
JoulePacket::write(CommPortPtr dev, QString &err)
{
QByteArray bytes = dataArrayForUnit();
const char *msg = cEscape(bytes.data(), bytes.count()).toAscii().constData();
if (JOULE_DEBUG) printf("writing '%s' to device\n", msg);
int n = dev->write(bytes.data(), bytes.count() , err); //
if (n != bytes.count()) {
if (n < 0) {
if (JOULE_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 (JOULE_DEBUG) printf("timeout writing %s to device\n", msg);
err = QString("timeout writing to device");
}
return false;
}
if (JOULE_DEBUG) printf("writing to device ok\n");
return true;
}
bool
JoulePacket::read(CommPortPtr dev, QString &err)
{
if (JOULE_DEBUG) printf("reading from device\n");
int n = dev->read(&startpattern, 2, err);
if (n <= 0) {
err = (n < 0) ? ("read header error: " + err) : "read timeout";
return false;
}
uint16_t _command = 0;
n = readOneByOne(dev, &_command, 2, err);
if (n <= 0) {
err = (n < 0) ? ("read command error: " + err) : "read timeout";
return false;
} else if (_command != command) {
err = "wrong response";
return false;
}
//if (JOULE_DEBUG) printf("command %s\n" , command);
// command already in checksum
n = readOneByOne(dev, &length, 2, err);
if (n <= 0) {
err = (n < 0) ? ("read length error: " + err) : "read timeout";
return false;
}
if (JOULE_DEBUG) printf("length %d\n" ,length);
checksum += (uint8_t)(length >> 8);
checksum += (uint8_t)(length & 0x00FF);
char buf[length];
n = readOneByOne(dev, &buf, length, err);
if (n <= 0) {
err = (n < 0) ? ("read error: " + err) : "read timeout";
return false;
} else if (n < length) {
err += QString(", read only %1 bytes insteed of: %2")
.arg(n).arg(length);
return false;
}
//if (JOULE_DEBUG) printf("payload %s\n" ,cEscape(buf,n).toAscii().constData());
addToPayload(buf,n);
char _checksum;
n = readOneByOne(dev, &_checksum, 1, err);
if (n <= 0) {
err = (n < 0) ? ("read checksum error: " + err) : "read timeout";
return false;
} else if (_checksum != checksum) {
err = "wrong _checksum";
return false;
}
return true;
}

62
src/JouleDevice.h Normal file
View File

@@ -0,0 +1,62 @@
#ifndef JOULEDEVICE_H
#define JOULEDEVICE_H
#include "CommPort.h"
#include "Device.h"
class DeviceFileInfo;
struct JouleDevices : public Devices
{
virtual DevicePtr newDevice( CommPortPtr dev, Device::StatusCallback cb );
virtual QString downloadInstructions() const;
virtual bool canCleanup( void ) {return true; };
};
struct JouleDevice : public Device
{
JouleDevice( CommPortPtr dev, StatusCallback cb ) :
Device( dev, cb ) {};
virtual bool download( const QDir &tmpdir,
QList<DeviceDownloadFile> &files,
CancelCallback cancelCallback,
ProgressCallback progressCallback,
QString &err);
virtual bool cleanup( QString &err );
bool getUnitVersion(QString &version, QByteArray &array, QString &err);
bool getUnitFreeSpace(QString &version, QString &err);
bool getSystemInfo(QString &version, QByteArray &array, QString &err);
bool getDownloadableRides(QList<DeviceStoredRideItem> &rides, QString &err);
};
class JoulePacket
{
public:
JoulePacket();
JoulePacket(uint16_t command);
void addToPayload(char bites);
void addToPayload(uint16_t bytes);
void addToPayload(char *bytes, int len);
bool verifyCheckSum(CommPortPtr dev, QString &err);
bool write(CommPortPtr dev, QString &err);
bool read(CommPortPtr dev, QString &err);
//char* data();
QByteArray dataArray();
QByteArray dataArrayForUnit();
uint16_t startpattern;
uint16_t command;
uint16_t length;
QByteArray payload;
char checksum;
private:
};
#endif // JOULEDEVICE_H

View File

@@ -317,6 +317,19 @@ Serial::name() const
return path;
}
bool
Serial::setBaudRate(int speed, QString &err)
{
/* not yet implemented
struct termios tty;
if (cfsetspeed(&tty, speed) == -1) {
perror("cfsetspeed");
assert(0);
}
*/
}
#ifndef Q_OS_WIN32
//
// Linux and Mac device enumerator matches wildcards in /dev
@@ -345,7 +358,7 @@ find_devices(char *result[], int capacity)
// /dev/ttyMI* - MOXA PCI cards
// /dev/rfcomm* - Bluetooth devices
if (regcomp(&reg,
"^(cu\\.(PL2303-[0-9A-F]+|ANTUSBStick.slabvcp|SLAB_USBtoUART|usbmodem[0-9A-F]+|usbserial-[0-9A-F]+|KeySerial[0-9])|ttyUSB[0-9]|ttyS[0-2]|ttyACM*|ttyMI*|rfcomm*)$",
"^(cu\\.(PL2303-[0-9A-F]+|ANTUSBStick.slabvcp|SLAB_USBtoUART|usbmodem[0-9A-F]+|usbserial-[0-9A-G]+|KeySerial[0-9])|ttyUSB[0-9]|ttyS[0-2]|ttyACM*|ttyMI*|rfcomm*)$",
REG_EXTENDED|REG_NOSUB)) {
assert(0);
}

View File

@@ -53,6 +53,7 @@ class Serial : public CommPort
virtual int read(void *buf, size_t nbyte, QString &err);
virtual int write(void *buf, size_t nbyte, QString &err);
virtual QString name() const;
virtual bool setBaudRate(int speed, QString &err);
};
#endif // _GC_PT_Serial_h

View File

@@ -177,7 +177,7 @@ SOURCES += ../qxt/src/qxtspanslider.cpp \
../qxt/src/qxtscheduleheaderwidget.cpp \
../qxt/src/qxtscheduleviewheadermodel_p.cpp \
../qxt/src/qxtscheduleitemdelegate.cpp \
../qxt/src/qxtstyleoptionscheduleviewitem.cpp
../qxt/src/qxtstyleoptionscheduleviewitem.cpp \
isEmpty( QTSOAP_INSTALL ) {
include( ../qtsolutions/soap/qtsoap.pri )
@@ -215,6 +215,7 @@ HEADERS += \
BatchExportDialog.h \
BestIntervalDialog.h \
BinRideFile.h \
Bin2RideFile.h \
BingMap.h \
CalendarDownload.h \
ChooseCyclistDialog.h \
@@ -266,6 +267,7 @@ HEADERS += \
IntervalItem.h \
IntervalSummaryWindow.h \
IntervalTreeView.h \
JouleDevice.h \
JsonRideFile.h \
LogTimeScaleDraw.h \
LogTimeScaleEngine.h \
@@ -396,6 +398,7 @@ SOURCES += \
BestIntervalDialog.cpp \
BikeScore.cpp \
BinRideFile.cpp \
Bin2RideFile.cpp \
BingMap.cpp \
CalendarDownload.cpp \
ChooseCyclistDialog.cpp \
@@ -452,6 +455,7 @@ SOURCES += \
IntervalItem.cpp \
IntervalSummaryWindow.cpp \
IntervalTreeView.cpp \
JouleDevice.cpp \
LeftRightBalance.cpp \
LogTimeScaleDraw.cpp \
LogTimeScaleEngine.cpp \