Files
GoldenCheetah/src/FileIO/SrmDevice.cpp
Mark Liversedge 4b5201c4c5 Restructure source directory
Introducing a directory structure to make it a bit less
daunting for new developers and perhaps even old hands.

The main folders all start with an upper character, so src
files are now located in;

* Core - Core data structures
* Gui - Main GUI elements
* Metrics - Models and Metrics
* FileIO - Device and File I/O
* Charts - All the chart types
* Cloud - Working with Web Resources
* Train - Anything Train View specific
* ANT - Our ANT+ Stack
* Resources - Images, Translations, Web etc

Apologies to anyone who needs to merge across this update.
2016-02-25 14:51:53 +00:00

634 lines
15 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 "SrmDevice.h"
#include "SrmRideFile.h"
#include <srmio.h>
#include <QMessageBox>
#include <errno.h>
static bool srm5Registered =
Devices::addType("SRM PCV", DevicesPtr(new SrmDevices( 5 )) );
static bool srm7Registered =
Devices::addType("SRM PCVI/7", DevicesPtr(new SrmDevices( 7 )));
DevicePtr
SrmDevices::newDevice( CommPortPtr dev )
{
return DevicePtr( new SrmDevice( dev, protoVersion));
}
bool
SrmDevices::supportsPort( CommPortPtr dev )
{
#if defined(SRMIO_HAVE_TERMIOS) || defined(SRMIO_HAVE_WINCOM)
// we could check device name starts with "com" or "/dev"
// but we wouldn't have got here unless it was a supported
// serial port anyway.
if( dev->type() == "Serial" )
return true;
#endif
#ifdef SRMIO_HAVE_D2XX
if( dev->type() == "D2XX" ){
switch( protoVersion ){
case 6:
case 7:
return true;
}
}
#endif
return false;
}
bool
SrmDevices::exclusivePort( CommPortPtr dev )
{
switch( protoVersion ){
case 5:
if( dev->type() == "Serial" && dev->name().contains( "PL2303" ) )
return true;
break;
case 6:
case 7:
if( dev->type() == "D2XX" && dev->name().startsWith( "POWERCONTROL" ) )
return true;
break;
}
return false;
}
bool
SrmDevice::get_tmpname(const QDir &tmpdir, QString &tmpname, QString &err)
{
QString tmpl = tmpdir.absoluteFilePath(".srmdl.XXXXXX.srm");
QTemporaryFile tmp(tmpl);
tmp.setAutoRemove(false);
if (!tmp.open()) {
err = tr("Failed to create temporary file ")
+ tmpl + ": " + tmp.error();
return false;
}
tmpname = tmp.fileName(); // after close(), tmp.fileName() is ""
tmp.close();
return true;
}
SrmDevice::~SrmDevice()
{
close();
}
// translate C callback function to method emitting signal
static void logfunc( const char *msg, void *data )
{
SrmDevice *dev = reinterpret_cast<SrmDevice*>(data);
dev->_emit_updateStatus( msg );
}
void
SrmDevice::_emit_updateStatus( QString statusText )
{
emit updateStatus( statusText );
}
bool
SrmDevice::open( QString &err )
{
srmio_error_t serr;
if( dev->type() == "Serial" ){
// no need to check device name starts with "com" or "/dev"
// since we wouldn't get this far unless it was a supported
// serial port device anyway.
#ifdef SRMIO_HAVE_WINCOM
io = srmio_iow32_new( dev->name().toLatin1().constData(), &serr );
if( ! io ){
err = tr("failed to allocate device handle: %1")
.arg(serr.message);
return false;
}
#elif defined(SRMIO_HAVE_TERMIOS)
io = srmio_ios_new( dev->name().toLatin1().constData(), &serr );
if( ! io ){
err = tr("failed to allocate device handle: %1")
.arg(serr.message);
return false;
}
#else
err = tr("device type %1 is unsupported")
.arg(dev->type());
return false;
#endif
} else if( dev->type() == "D2XX" ){
#ifdef SRMIO_HAVE_D2XX
io = srmio_d2xx_description_new(
dev->name().toLatin1().constData(), &serr );
if( ! io ){
err = tr("failed to allocate device handle: %1")
.arg(serr.message);
return false;
}
#else
err = tr("device type %1 is unsupported")
.arg(dev->type());
return false;
#endif
} else {
err = tr("device type %1 is unsupported")
.arg(dev->type());
return false;
}
switch( protoVersion ){
case 5:
emit updateStatus( tr("opening PCV at %1").arg(dev->name()) );
pc = srmio_pc5_new( &serr );
break;
case 6:
case 7:
emit updateStatus( tr("opening PC6/7 at %1").arg(dev->name()) );
pc = srmio_pc7_new( &serr );
break;
default:
err = tr("unsupported SRM Protocol version: %1")
.arg(protoVersion);
goto fail;
}
if( ! pc ){
err = tr("failed to allocate Powercontrol handle: %1")
.arg(serr.message);
goto fail;
}
if( ! srmio_io_open( io, &serr ) ){
err = tr("Couldn't open device %1: %2")
.arg(dev->name())
.arg(serr.message);
goto fail;
}
if( ! srmio_pc_set_logfunc( pc, logfunc,
reinterpret_cast<void*>( this ), &serr ) ){
err = tr("Couldn't set logging function: %1")
.arg(serr.message);
goto fail;
}
if( ! srmio_pc_set_device( pc, io, &serr ) ){
err = tr("failed to set Powercontrol io handle: %1")
.arg(serr.message);
goto fail;
}
if( ! srmio_pc_open( pc, &serr ) ){
err = tr("failed to initialize Powercontrol communication: %1")
.arg(serr.message);
goto fail;
}
is_open = true;
return true;
fail:
if( pc ){
srmio_pc_free( pc );
pc = NULL;
}
if( io ){
srmio_io_free( io );
io = NULL;
}
return false;
}
bool
SrmDevice::close( void )
{
rideList.clear();
if( pc ){
srmio_pc_free( pc );
pc = NULL;
}
if( io ){
srmio_io_free( io );
io = NULL;
}
is_open = false;
return true;
}
bool
SrmDevice::preview( QString &err )
{
srmio_error_t serr;
size_t block_cnt;
struct _srmio_pc_xfer_block_t block;
if( ! is_open ){
if( ! open( err ) )
return false;
}
rideList.clear();
if( ! srmio_pc_can_preview( pc ) )
// nothing to do
return true;
if( ! srmio_pc_xfer_start( pc, &serr ) ){
err = tr("failed to start download: %1")
.arg(serr.message);
goto fail;
}
if( ! srmio_pc_xfer_get_blocks( pc, &block_cnt, &serr ) ){
err = tr("failed to get number of data blocks: %1")
.arg(serr.message);
goto fail;
}
emit updateStatus(tr("found %1 ride blocks").arg(block_cnt));
while( srmio_pc_xfer_block_next( pc, &block )){
DeviceRideItemPtr ride( new DeviceRideItem );
if( block.start )
ride->startTime.setTime_t( 0.1 * block.start );
if( block.end )
ride->endTime.setTime_t( 0.1 * block.end );
ride->work = block.total;
ride->wanted = false;
rideList.append( ride );
if( block.athlete )
free( block.athlete );
block.athlete = NULL;
}
if( srmio_pc_xfer_state_success != srmio_pc_xfer_status( pc, &serr ) ){
err = tr( "preview failed: %1")
.arg(serr.message);
goto fail;
}
if( ! srmio_pc_xfer_finish( pc, &serr ) ){
err = tr("preview failed: %1")
.arg(serr.message);
goto fail;
}
return true;
fail:
rideList.clear();
return false;
}
bool
SrmDevice::download( const QDir &tmpdir,
QList<DeviceDownloadFile> &files,
QString &err)
{
srmio_error_t serr;
struct _srmio_pc_xfer_block_t block;
srmio_data_t data( NULL );
srmio_data_t *splitList( NULL );
srmio_data_t *split;
int mfirst( -1 );
size_t block_cnt, block_num( 0 );
size_t prog_sum( 0 ), prog_prev( 0 );
size_t chunks_done( 0 );
srmio_time_t splitGap( 72000 ); // 2h - NOTE: we could make this configurable
if( ! is_open ){
if( ! open( err ) )
return false;
}
// fetch preview in case user didn't
if( srmio_pc_can_preview(pc) && rideList.size() == 0 ){
if( ! preview( err ) )
return false;
}
data = srmio_data_new( &serr );
if( ! data ){
err = tr("failed to allocate data handle: %1")
.arg(serr.message);
goto fail;
}
if( m_Cancelled ){
err = tr("download cancelled");
goto fail;
}
if( ! srmio_pc_xfer_start( pc, &serr )){
err = tr("failed to start download: %1")
.arg(serr.message);
goto fail;
}
if( ! srmio_pc_xfer_get_blocks( pc, &block_cnt, &serr ) ){
err = tr("failed to get number of data blocks: %1")
.arg(serr.message);
goto fail1;
}
for( int i = 0; i < rideList.size(); ++i ){
if( rideList.at(i)->wanted )
prog_sum += rideList.at(i)->work;
}
while( srmio_pc_xfer_block_next( pc, &block )){
bool wanted = false;
struct _srmio_chunk_t chunk;
bool is_int;
bool is_first;
size_t prog_total;
if( rideList.empty() ){
wanted = true;
} else {
for( int i = 0; i < rideList.size(); ++i ){
if( rideList.at(i)->startTime.toTime_t() == block.start / 10 ){
wanted = rideList.at(i)->wanted;
break;
}
}
}
if( ! wanted ){
emit updateStatus(tr("skipping unselected ride block %1")
.arg(block_num +1));
++block_num;
continue;
}
data->slope = block.slope;
data->zeropos = block.zeropos;
data->circum = block.circum;
if( block.athlete ){
if( data->athlete )
free( data->athlete );
data->athlete = strdup( block.athlete );
}
if( ! rideList.empty() ){
prog_total = prog_sum;
} else if( block_cnt == 1 ){
prog_total = block.total;
} else {
prog_total = block_cnt * 1000;
}
while( srmio_pc_xfer_chunk_next( pc, &chunk, &is_int, &is_first ) ){
if( m_Cancelled ){
err = tr("download cancelled");
goto fail1;
}
if( chunks_done % 16 == 0 ){
size_t block_done;
srmio_pc_xfer_block_progress( pc, &block_done );
if( ! rideList.empty() ){
block_done += prog_prev;
} else if( block_cnt == 1 ){
// unchanged
} else {
block_done = (double)block_num * 1000
+ 1000 * block.total / block_done;
}
emit updateProgress( tr("progress: %1/%2")
.arg(block_done)
.arg(prog_total));
}
if( ! srmio_data_add_chunk( data, &chunk, &serr ) ){
err = tr("adding chunk failed: %1")
.arg(serr.message);
goto fail1;
}
++chunks_done;
/* finish previous marker */
if( mfirst >= 0 && ( ! is_int || is_first ) )
if( ! srmio_data_add_marker( data, mfirst, data->cused -2, &serr ) ){
err = tr("adding marker failed: %1")
.arg(serr.message);
goto fail1;
}
/* start marker */
if( is_first ){
mfirst = (int)data->cused -1;
} else if( ! is_int ){
mfirst = -1;
}
}
/* finalize marker at block end */
if( mfirst >= 0 ){
if( ! srmio_data_add_marker( data, mfirst, data->cused -1, &serr ) ){;
err = tr("adding marker failed: %1")
.arg(serr.message);
goto fail1;
}
mfirst = -1;
}
if( ! rideList.empty() )
prog_prev += block.total;
else
prog_prev += 1000;
if( block.athlete )
free( block.athlete );
block.athlete = NULL;
++block_num;
}
if( srmio_pc_xfer_state_success != srmio_pc_xfer_status( pc, &serr ) ){
err = tr( "download failed: %1")
.arg(serr.message);
goto fail;
}
if( ! srmio_pc_xfer_finish( pc, &serr ) ){
err = tr( "download failed: %1")
.arg(serr.message);
goto fail;
}
emit updateStatus( tr("got %1 records").arg(data->cused) );
if( m_Cancelled ){
err = tr("download cancelled");
goto fail;
}
if( ! data->cused ){
err = tr("no data available");
goto fail;
}
splitList = srmio_data_split( data, splitGap, 1000, &serr );
if( ! splitList ){
err = tr("Couldn't split data: %1")
.arg(serr.message);
goto fail;
}
for( split = splitList; *split; ++split ){
FILE *fh( NULL );
srmio_time_t stime;
srmio_data_t fixed;
DeviceDownloadFile file;
// skip empty hunks ... shouldn't happen, just to be safe
if( ! (*split)->cused ){
continue;
}
fixed = srmio_data_fixup( *split, &serr );
if( ! fixed ){
err = tr("Couldn't fixup data: %1")
.arg(serr.message);
goto fail;
}
// skip empty hunks ... shouldn't happen, just to be safe
if( ! fixed->cused ){
srmio_data_free(fixed);
continue;
}
file.extension = "srm";
if (!get_tmpname(tmpdir, file.name, err))
goto fail;
if( ! srmio_data_time_start( fixed, &stime, &serr ) ){
srmio_data_free(fixed);
err = tr("Couldn't get start time of data: %1")
.arg(serr.message);
goto fail;
}
file.startTime.setTime_t( 0.1 * stime );
fh = fopen( file.name.toLatin1().constData(), "wb" );
if( ! fh ){
srmio_data_free(fixed);
err = tr( "failed to open file %1: %2")
.arg(file.name)
.arg(strerror(errno));
goto fail;
}
if( ! srmio_file_srm7_write(fixed, fh, &serr) ){
srmio_data_free(fixed);
err = tr("Couldn't write to file %1: %2")
.arg(file.name)
.arg(serr.message);
fclose(fh);
goto fail;
}
files.append(file);
fclose( fh );
srmio_data_free(fixed);
}
for( split = splitList; *split; ++split )
srmio_data_free( *split );
free(splitList);
srmio_data_free( data );
return true;
fail1:
srmio_pc_xfer_finish(pc, NULL);
fail:
if( data ) srmio_data_free( data );
if( splitList ){
for( split = splitList; *split; ++split )
srmio_data_free( *split );
free(splitList);
}
close();
return false;
}
bool
SrmDevice::cleanup( QString &err )
{
srmio_error_t serr;
if( ! is_open ){
if( ! open( err ) )
goto cleanup;
}
emit updateStatus( tr("cleaning device ..."));
if( ! srmio_pc_cmd_clear( pc, &serr ) ){
err = tr("failed to clear Powercontrol memory: %1")
.arg(serr.message);
goto cleanup;
}
return true;
cleanup:
close();
return false;
}