From 66570337b395beed66a3ad69d6545270d01ac867 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 3 Nov 2014 20:30:35 +0000 Subject: [PATCH] Basic Moxy Download 1 of 2 .. device IO is working ok .. need to add in processing and creation of a ridefile in part 2 --- src/Device.h | 3 + src/DownloadRideDialog.cpp | 28 ++-- src/JouleDevice.cpp | 2 +- src/MoxyDevice.cpp | 304 +++++++++++++++++++++++++++++++++++++ src/MoxyDevice.h | 45 ++++++ src/Serial.cpp | 46 +++++- src/src.pro | 2 + 7 files changed, 408 insertions(+), 22 deletions(-) create mode 100644 src/MoxyDevice.cpp create mode 100644 src/MoxyDevice.h diff --git a/src/Device.h b/src/Device.h index 8745a3e3d..fdf668ec2 100644 --- a/src/Device.h +++ b/src/Device.h @@ -100,6 +100,9 @@ struct Devices // cleanup for this device type is implemented: virtual bool canCleanup() { return false; }; + // can preview ride list + virtual bool canPreview() { return true; }; // assume it can ! + virtual QString downloadInstructions() const { return ""; }; static QList typeNames(); diff --git a/src/DownloadRideDialog.cpp b/src/DownloadRideDialog.cpp index d649fcb93..f215b8f85 100644 --- a/src/DownloadRideDialog.cpp +++ b/src/DownloadRideDialog.cpp @@ -109,7 +109,7 @@ DownloadRideDialog::setReadyInstruct() if (inst.size() == 0) statusLabel->setPlainText(tr("Click Download to begin downloading.")); else - statusLabel->setPlainText(inst + tr(", \nthen click Download.")); + statusLabel->setPlainText(inst + tr(", then click Download.")); updateAction( actionIdle ); } @@ -287,12 +287,14 @@ DownloadRideDialog::downloadClicked() connect( this, SIGNAL(cancel()), device.data(), SLOT(cancelled()) ); connect( device.data(), SIGNAL(updateProgress(QString)), this, SLOT(updateProgress(QString))); - updateStatus(tr("getting summary ...")); - if( ! device->preview( err ) ){ + if (devtype->canPreview()) { + updateStatus(tr("Getting ride list ...")); + if( ! device->preview( err ) ){ - QMessageBox::information(this, tr("Preview failed"), err); - updateAction( actionIdle ); - return; + QMessageBox::information(this, tr("Get ride list failed"), err); + updateAction( actionIdle ); + return; + } } QList &rides( device->rides() ); @@ -304,11 +306,11 @@ DownloadRideDialog::downloadClicked() } } - updateStatus(tr("getting data ...")); + updateStatus(tr("Starting Download ...")); if (!device->download( context->athlete->home->downloads(), files, err)) { if (cancelled) { - QMessageBox::information(this, tr("Download canceled"), + QMessageBox::information(this, tr("Download cancelled"), tr("Cancel clicked by user.")); cancelled = false; } @@ -325,7 +327,7 @@ DownloadRideDialog::downloadClicked() int failures = 0; for( int i = 0; i < files.size(); ++i ){ if( ! files.at(i).startTime.isValid() ){ - updateStatus(tr("file %1 has no valid timestamp, falling back to 'now'") + updateStatus(tr("File %1 has no valid timestamp, falling back to 'now'") .arg(files.at(i).name)); files[i].startTime = QDateTime::currentDateTime(); } @@ -379,7 +381,7 @@ DownloadRideDialog::downloadClicked() .arg(files.at(i).name) .arg(filepath) .arg(strerror(errno)) ); - updateStatus(tr("failed to rename %1 to %2") + updateStatus(tr("Failed to rename %1 to %2") .arg( files.at(i).name ) .arg( filename )); QFile::remove(files.at(i).name); @@ -429,7 +431,7 @@ DownloadRideDialog::downloadClicked() } if( ! failures ) - updateStatus( tr("download completed successfully") ); + updateStatus( tr("Download completed") ); updateAction( actionIdle ); } @@ -458,7 +460,7 @@ DownloadRideDialog::eraseClicked() QString err; if( device->cleanup( err) ) - updateStatus( tr("cleaned data") ); + updateStatus( tr("Cleaned data") ); else updateStatus( err ); @@ -486,5 +488,3 @@ DownloadRideDialog::closeClicked() { accept(); } - - diff --git a/src/JouleDevice.cpp b/src/JouleDevice.cpp index e6b2e2a56..c81a72cb1 100644 --- a/src/JouleDevice.cpp +++ b/src/JouleDevice.cpp @@ -45,7 +45,7 @@ static bool jouleRegistered = QString JouleDevices::downloadInstructions() const { - return (tr("Make sure the Joule (1.0 or GPS) unit is turned ON\n")); + return (tr("Make sure the Joule (1.0 or GPS) unit is turned ON")); } DevicePtr diff --git a/src/MoxyDevice.cpp b/src/MoxyDevice.cpp new file mode 100644 index 000000000..7178a2388 --- /dev/null +++ b/src/MoxyDevice.cpp @@ -0,0 +1,304 @@ +/* + * 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 "MoxyDevice.h" +#include "DownloadRideDialog.h" +#include "PowerTapUtil.h" +#include "Device.h" +#include "RideFile.h" + +#include +#include +#include + +#include // for msleep + +static bool moxyRegistered = + Devices::addType("Moxy Muscle Oxygen Monitor", DevicesPtr(new MoxyDevices()) ); + +QString +MoxyDevices::downloadInstructions() const +{ + return (tr("NOT WORKING YET .. DO NOT USE\nMake sure the Moxy is connected via USB")); +} + +DevicePtr +MoxyDevices::newDevice( CommPortPtr dev ) +{ + return DevicePtr( new MoxyDevice( dev )); +} + +bool +MoxyDevice::download( const QDir &tmpdir, + QList &files, + QString &err) +{ Q_UNUSED(tmpdir); + Q_UNUSED(files); + + int bytes=0; + char vbuf[256]; + + QString deviceInfo; + + if (!dev->open(err)) { + err = tr("ERROR: open failed: ") + err; + return false; + } + dev->setBaudRate(115200, err); + + // get into engineering mode + if (writeCommand(dev, "\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } + + if ((bytes=readUntilPrompt(dev, vbuf, 256, err)) <= 0) { + return false; + } + + if (writeCommand(dev, "\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } + + if ((bytes=readUntilPrompt(dev, vbuf, 256, err)) <= 0) { + return false; + } + + // now get a prompt + if (writeCommand(dev, "\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } + + // get a prompt back ? + if ((bytes=readUntilPrompt(dev, vbuf, 256, err)) <= 0) { + return false; + } + + // now get version + if (writeCommand(dev, "ver\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } else if ((bytes=readUntilPrompt(dev, vbuf, 256, err)) > 0) { + vbuf[bytes-1] = '\0'; + deviceInfo += vbuf; + } else { + return false; + } + + // now get time on unit + if (writeCommand(dev, "time\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } else if ((bytes=readUntilPrompt(dev, vbuf, 256, err)) > 0) { + vbuf[bytes-1] = '\0'; + deviceInfo += vbuf; + } else { + return false; + } + + // now get battery status + if (writeCommand(dev, "batt\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } else if ((bytes=readUntilPrompt(dev, vbuf, 256, err)) > 0) { + vbuf[bytes-1] = '\0'; + deviceInfo += vbuf; + } else { + return false; + } + + // now get update mode + if (writeCommand(dev, "um\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } else if ((bytes=readUntilPrompt(dev, vbuf, 256, err)) > 0) { + vbuf[bytes-1] = '\0'; + deviceInfo += vbuf; + } else { + return false; + } + + emit updateStatus(deviceInfo); + + // now lets get the data + if (writeCommand(dev, "gd\r", err) == false) { + emit updateStatus(QString(tr("Write error: %1\n")).arg(err)); + return false; + } + emit updateStatus(deviceInfo); + emit updateStatus(QString(tr("Downloading ... \n"))); + + do { + if ((bytes=readData(dev, vbuf, 256, err)) > 0) { + vbuf[bytes] = '\0'; + qDebug()<close(); + + return true; +} + + +bool +MoxyDevice::cleanup( QString &err ) { + emit updateStatus(tr("Erase all records on computer")); + + // open device + if (!dev->open(err)) { + err = tr("ERROR: open failed: ") + err; + } + + dev->setBaudRate(115200, err); + + dev->close(); + + return true; +} + +static bool +hasNewline(const char *buf, int len) +{ + static char newline[] = { 0x0d, 0x0a }; + if (len < 2) + return false; + for (int i = 0; i < len; ++i) { + bool success = true; + for (int j = 0; j < 2; ++j) { + if (buf[i+j] != newline[j]) { + success = false; + break; + } + } + if (success) + return true; + } + return false; +} + +static QString +cEscape(const 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; +} + +// read the entire response after a command issued it ends with +// a command prompt +int +MoxyDevice::readUntilPrompt(CommPortPtr dev, char *buf, int len, QString &err) +{ + + int sofar = 0; + while (sofar < len) { + // Read one byte at a time to avoid waiting for timeout. + int n = dev->read(buf + sofar, 1, err); + if (n <= 0) { + err = (n < 0) ? (tr("read error: ") + err) : tr("read timeout"); + err += QString(tr(", read %1 bytes so far: \"%2\"")) + .arg(sofar).arg(sofar > 0 ? cEscape(buf, sofar) : ""); + return -1; + } + + sofar += n; + + // we got our prompt + if (*(buf+sofar-1) == '>') break; + } + return sofar; +} + +// just read whatever we get before the new line +int +MoxyDevice::readUntilNewline(CommPortPtr dev, char *buf, int len, QString &err) +{ + int sofar = 0; + while (!hasNewline(buf, sofar) && sofar < len) { + // Read one byte at a time to avoid waiting for timeout. + int n = dev->read(buf + sofar, 1, err); + if (n <= 0) { + err = (n < 0) ? (tr("read error: ") + err) : tr("read timeout"); + err += QString(tr(", read %1 bytes so far: \"%2\"")) + .arg(sofar).arg(cEscape(buf, sofar)); + return -1; + } + sofar += n; + } + return sofar; +} + +// read a line of data, if it starts with a prompt ">" then its done +int +MoxyDevice::readData(CommPortPtr dev, char *buf, int len, QString &err) +{ + int sofar = 0; + while (sofar < len) { + // Read one byte at a time to avoid waiting for timeout. + int n = dev->read(buf + sofar, 1, err); + if (n <= 0) { + err = (n < 0) ? (tr("read error: ") + err) : tr("read timeout"); + err += QString(tr(", read %1 bytes so far: \"%2\"")) + .arg(sofar).arg(cEscape(buf, sofar)); + return -1; + } + + sofar += n; + + if (hasNewline(buf, sofar) || *buf == '>') { + break; + } + } + return sofar; +} + +bool +MoxyDevice::writeCommand(CommPortPtr dev, const char *command, QString &err) +{ + QThread::msleep(100); // wait a tenth of a second + + int len = strlen(command); + int n = dev->write(const_cast(command), len, err); + + if (n != len) { + if (n < 0) + err = QString(tr("failed to write '%1' to device: %2")).arg(const_cast(command)).arg(err); + else + err = QString(tr("timeout writing '%1' to device")).arg(const_cast(command)); + return false; + } + return true; +} diff --git a/src/MoxyDevice.h b/src/MoxyDevice.h new file mode 100644 index 000000000..ffa269c33 --- /dev/null +++ b/src/MoxyDevice.h @@ -0,0 +1,45 @@ +#ifndef JOULEDEVICE_H +#define JOULEDEVICE_H + +#include "CommPort.h" +#include "Device.h" +#include "stdint.h" + +class DeviceFileInfo; + +struct MoxyDevices : public Devices +{ + Q_DECLARE_TR_FUNCTIONS(MoxyDevices) + + public: + virtual DevicePtr newDevice( CommPortPtr dev ); + virtual QString downloadInstructions() const; + virtual bool canCleanup( void ) {return true; }; + virtual bool canPreview() { return false; }; // Moxy is dumb ass +}; + +struct MoxyDevice : public Device +{ + Q_DECLARE_TR_FUNCTIONS(MoxyDevice) + + public: + MoxyDevice( CommPortPtr dev ) : Device( dev ) {}; + + // io with the moxy is text based lines + // they even give you a prompt ">" when + // the command completes + int readUntilPrompt(CommPortPtr dev, char *buf, int len, QString &err); + int readUntilNewline(CommPortPtr dev, char *buf, int len, QString &err); + int readData(CommPortPtr dev, char *buf, int len, QString &err); + + bool writeCommand(CommPortPtr dev, const char *command, QString &err); + + virtual bool download( const QDir &tmpdir, + QList &files, + QString &err); + + virtual bool cleanup( QString &err ); + +}; + +#endif // JOULEDEVICE_H diff --git a/src/Serial.cpp b/src/Serial.cpp index 4a71ac729..1946dc1c7 100644 --- a/src/Serial.cpp +++ b/src/Serial.cpp @@ -112,6 +112,7 @@ Serial::open(QString &err) perror("tcsetattr"); assert(0); } + tcflush(fd, TCIOFLUSH); // clear out the garbage return true; #else Q_UNUSED(err); @@ -171,6 +172,7 @@ Serial::close() { #ifndef Q_OS_WIN32 assert(fd >= 0); + tcflush(fd, TCIOFLUSH); // clear out the garbage ::close(fd); fd = -1; #else @@ -319,15 +321,45 @@ Serial::name() const bool Serial::setBaudRate(int speed, QString &err) -{ Q_UNUSED(speed); Q_UNUSED(err); - /* not yet implemented +{ - struct termios tty; - if (cfsetspeed(&tty, speed) == -1) { - perror("cfsetspeed"); - assert(0); + // only really needed for Moxy + // so not doing a big old switch/case + if (speed == 115200) { + +#ifndef Q_OS_WIN32 + + // LINUX / MAC + struct termios tty; + if (tcgetattr(fd, &tty) == -1) { + perror("tcgetattr"); + } + cfsetspeed(&tty, B115200); + + /* put terminal in raw mode after flushing */ + if (tcsetattr(fd,TCSAFLUSH,&tty) < 0) { + qDebug()<<"cannot set raw mode"; + return false; + } +#else + + // WINDOWS + + DCB deviceSettings; // serial port settings baud rate et al + + // get current settings + if (GetCommState (fd, &deviceSettings) == false) + return false; + + // set the baud rate then + deviceSettings.BaudRate = CBR_115200; + + // apply + if (SetCommState(fd, &deviceSettings) == false) + return false; +#endif + return true; // this worked } - */ return false; } diff --git a/src/src.pro b/src/src.pro index 0fdb6a224..9a5a8352a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -363,6 +363,7 @@ HEADERS += \ MergeActivityWizard.h \ MetadataWindow.h \ MetricAggregator.h \ + MoxyDevice.h \ NewCyclistDialog.h \ NullController.h \ PaceZones.h \ @@ -574,6 +575,7 @@ SOURCES += \ MergeActivityWizard.cpp \ MetadataWindow.cpp \ MetricAggregator.cpp \ + MoxyDevice.cpp \ NewCyclistDialog.cpp \ NullController.cpp \ PaceTimeInZone.cpp \