/* * 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 #include #include 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; } static bool 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 = "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(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().toAscii().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().toAscii().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().toAscii().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 Protocl 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( 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 &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.toAscii().constData(), "w" ); 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; }