initial import

This commit is contained in:
Sean C. Rhea
2006-09-06 23:59:42 +00:00
commit aae0d3c116
55 changed files with 4761 additions and 0 deletions

340
COPYING Normal file
View File

@@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

53
Makefile Normal file
View File

@@ -0,0 +1,53 @@
#
# $Id: Makefile,v 1.11 2006/09/06 23:23:03 srhea Exp $
#
# Copyright (c) 2006 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
#
CC=gcc
CFLAGS=-g -W -Wall -Werror -ansi -pedantic
CXXFLAGS=-g -W -Wall -Werror
all: ptdl ptpk ptunpk cpint subdirs
.PHONY: all clean subdirs
clean:
rm -f *.o ptdl ptpk ptunpk cpint
$(MAKE) -wC gui clean
subdirs:
@if test -d gui; \
then \
if ! test -f gui/Makefile; then cd gui; qmake; cd -; fi; \
$(MAKE) -wC gui; \
fi
cpint: cpint-cmd.o cpint.o pt.o
$(CXX) -o $@ $^
ptdl: ptdl.o pt.o
$(CC) -o $@ $^
ptunpk: ptunpk.o pt.o
$(CC) -o $@ $^
ptpk: ptpk.o pt.o
$(CC) -o $@ $^
ptdl.o: pt.h
ptunpk.o: pt.h

83
compare_rows.pl Executable file
View File

@@ -0,0 +1,83 @@
#!/usr/bin/perl -w
#
# $Id: compare_rows.pl,v 1.1 2006/05/12 21:50:51 srhea Exp $
#
# Copyright (c) 2006 Russ Cox (rsc@swtch.com) and Sean 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
#
use strict;
if ($#ARGV < 1) {
print STDERR "usage: compare_rows.pl <file 1> <file 2> [column]\n";
exit 1;
}
my $file1 = $ARGV[0];
my $file2 = $ARGV[1];
my $start_col = 0;
my $stop_col = 8;
if ($#ARGV >= 2) {
my $stop_col = $ARGV[2];
my $start_col = $stop_col - 1;
}
open(FILE1, $file1) or die "Couldn't open $file1\n";
open(FILE2, $file2) or die "Couldn't open $file2\n";
my $row = 0;
while(<FILE1>) {
++$row;
$_ =~ s/^\s+//;
my @cols1 = split /\s+/, $_;
my $row2 = <FILE2>;
if ($row2) {
my @cols2 = split /\s+/, $row2;
my $col;
for ($col = $start_col; $col < $stop_col; ++$col) {
if (($cols1[$col] =~ m/[0-9.]+/) && ($cols2[$col] =~ m/[0-9.]+/)) {
if ($col == 0) {
# Times are sometimes off by a bit; just make sure they're
# within a tenth of a percent.
if (($cols1[$col] * 0.999 > $cols2[$col])
|| ($cols1[$col] * 1.001 < $cols2[$col])) {
print "$file1: @cols1\n";
print "$file2: @cols2\n";
}
}
else {
# All other columns should match exactly.
if ($cols1[$col] != $cols2[$col]) {
print "$file1: @cols1\n";
print "$file2: @cols2\n";
}
}
}
elsif (!($cols1[$col] eq $cols2[$col])) {
print "$file1: @cols1\n";
print "$file2: @cols2\n";
}
}
}
else {
print "$file1 is longer.\n";
}
}
if (<FILE2>) {
print "$file2 is longer.\n";
}

55
cpint-cmd.c Normal file
View File

@@ -0,0 +1,55 @@
/*
* $Id: cpint-cmd.c,v 1.1 2006/08/11 19:53:07 srhea Exp $
*
* Copyright (c) 2006 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 <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <dirent.h>
#include <math.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cpint.h"
static void
one_done_cb(const char *longdate)
{
fprintf(stderr, "Compiling data for ride on %s...", longdate);
}
int
main(int argc, char *argv[])
{
int i;
double *bests;
int bestlen;
char *dir = ".";
if (argc > 1)
dir = argv[1];
update_cpi_files(dir, one_done_cb);
combine_cpi_files(dir, &bests, &bestlen);
for (i = 0; i < bestlen; ++i) {
if (bests[i] != 0)
printf("%6.3f %3.0f\n", i * 0.021, round(bests[i]));
}
return 0;
}

251
cpint.c Normal file
View File

@@ -0,0 +1,251 @@
/*
* $Id: cpint.c,v 1.4 2006/08/11 19:53:07 srhea Exp $
*
* Copyright (c) 2006 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 <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <dirent.h>
#include <math.h>
#include <regex.h>
#include <stdlib.h>
#include <string.h>
#include "pt.h"
struct point
{
double secs;
int watts;
struct point *next;
};
static struct point *head, *tail;
time_t start_since_epoch;
static double last_secs;
static int rec_int_ms = 1.26 * 1000.0;
static int
secs_to_interval(double secs)
{
if (rec_int_ms == 0) {
fprintf(stderr, "Missing recording interval.\n");
exit(1);
}
return (int) round(secs * 1000.0 / rec_int_ms);
}
static void
add_point(double secs, double watts)
{
struct point *p = (struct point*) malloc(sizeof(struct point));
p->secs = secs;
p->watts = watts;
p->next = NULL;
if (tail) tail->next = p; else head = p;
tail = p;
}
static void
time_cb(struct tm *time, time_t since_epoch, void *context)
{
double secs;
context = NULL;
time = NULL;
if (start_since_epoch == 0)
start_since_epoch = since_epoch;
secs = since_epoch - start_since_epoch;
/* Be conservative: a sleep interval counts as all zeros. */
add_point(secs, 0.0);
last_secs = secs;
}
static void
data_cb(double secs, double nm, double mph, double watts, double miles,
unsigned cad, unsigned hr, unsigned interval, void *context)
{
context = NULL;
nm = 0.0; miles = 0.0; cad = 0; hr = 0; interval = 0;
/* Be conservative: count NaN's as zeros. */
add_point(secs, (mph == -1.0) ? 0.0 : watts);
last_secs = secs;
}
static void
error_cb(const char *msg, void *context)
{
context = NULL;
fprintf(stderr, "%s\n", msg);
exit(1);
}
void
one_file(FILE *in, FILE *out)
{
double start_secs, prev_secs, dur_secs, avg, sum;
int dur_ints, i, total_intervals;
double *bests;
struct point *p, *q;
if (head) {
p = head;
while (p) { q = p; p = p->next; free(q); }
head = tail = NULL;
}
start_since_epoch = 0;
last_secs = 0.0;
pt_read_raw(in, 0 /* not compat */, NULL, NULL,
&time_cb, &data_cb, &error_cb);
total_intervals = secs_to_interval(tail->secs);
bests = (double*) calloc(total_intervals + 1, sizeof(double));
start_secs = prev_secs = 0.0;
for (p = head; p; p = p->next) {
sum = 0.0;
for (q = p; q; q = q->next) {
sum += (q->secs - prev_secs) * q->watts;
dur_secs = q->secs - start_secs;
dur_ints = secs_to_interval(dur_secs);
avg = sum / dur_secs;
if (bests[dur_ints] < avg)
bests[dur_ints] = avg;
prev_secs = q->secs;
}
start_secs = prev_secs = p->secs;
}
for (i = 0; i <= total_intervals; ++i) {
if (bests[i] != 0)
fprintf(out, "%6.3f %3.0f\n", i * rec_int_ms / 1000.0 / 60.0,
round(bests[i]));
}
}
void
update_cpi_files(const char *dir, void (*one_done_cb)(const char *date))
{
DIR *dirp;
struct dirent *dp;
regex_t reg;
struct stat sbi, sbo;
FILE *in, *out;
char *outname;
char year[5], mon[3], day[3], hour[3], min[3], sec[3];
char longdate[26];
struct tm time;
time_t t;
int nmatch = 7;
regmatch_t *pmatch = (regmatch_t*) calloc(nmatch, sizeof(regmatch_t));
if (regcomp(&reg, "^([0-9][0-9][0-9][0-9])_([0-9][0-9])_([0-9][0-9])"
"_([0-9][0-9])_([0-9][0-9])_([0-9][0-9])\\.raw$", REG_EXTENDED))
assert(0);
outname = malloc(strlen(dir) + 25);
dirp = opendir(dir);
while ((dp = readdir(dirp)) != NULL) {
if (regexec(&reg, dp->d_name, nmatch, pmatch, 0) == 0) {
if (stat(dp->d_name, &sbi))
assert(0);
sprintf(outname, "%s/%s", dir, dp->d_name);
strcpy(outname + strlen(outname) - 4, ".cpi");
if ((stat(outname, &sbo)) || (sbo.st_mtime < sbi.st_mtime)) {
strncpy(year, dp->d_name + pmatch[1].rm_so, 4); year[4] = '\0';
strncpy(mon, dp->d_name + pmatch[2].rm_so, 2); mon[2] = '\0';
strncpy(day, dp->d_name + pmatch[3].rm_so, 2); day[2] = '\0';
strncpy(hour, dp->d_name + pmatch[4].rm_so, 2); hour[2] = '\0';
strncpy(min, dp->d_name + pmatch[5].rm_so, 2); min[2] = '\0';
strncpy(sec, dp->d_name + pmatch[6].rm_so, 2); sec[2] = '\0';
memset(&time, 0, sizeof(time));
time.tm_year = atoi(year) - 1900;
time.tm_mon = atoi(mon) - 1;
time.tm_mday = atoi(day);
time.tm_hour = atoi(hour);
time.tm_min = atoi(min);
time.tm_sec = atoi(sec);
time.tm_isdst = -1;
t = mktime(&time);
assert(t != -1);
ctime_r(&t, longdate);
longdate[24] = '\0'; /* get rid of newline */
one_done_cb(longdate);
fflush(stderr);
in = fopen(dp->d_name, "r");
assert(in);
out = fopen(outname, "w");
assert(out);
one_file(in, out);
fclose(in);
fclose(out);
fprintf(stderr, "done.\n");
}
}
}
closedir(dirp);
free(pmatch);
}
void
combine_cpi_files(const char *dir, double *bests[], int *bestlen)
{
DIR *dirp;
struct dirent *dp;
FILE *in;
char line[40];
int lineno;
double mins;
int watts;
double *tmp;
int interval;
*bestlen = 1000;
*bests = calloc(*bestlen, sizeof(double));
dirp = opendir(dir);
while ((dp = readdir(dirp)) != NULL) {
if (strcmp(".cpi", dp->d_name + dp->d_namlen - 4) == 0) {
in = fopen(dp->d_name, "r");
assert(in);
lineno = 1;
while (fgets(line, sizeof(line), in) != NULL) {
if (sscanf(line, "%lf %d\n", &mins, &watts) != 2) {
fprintf(stderr, "Bad match on line %d: %s", lineno, line);
exit(1);
}
interval = secs_to_interval(mins * 60.0);
while (interval >= *bestlen) {
tmp = calloc(*bestlen * 2, sizeof(double));
memcpy(tmp, *bests, *bestlen * sizeof(double));
free(*bests);
*bests = tmp;
*bestlen *= 2;
}
if ((*bests)[interval] < watts)
(*bests)[interval] = watts;
++lineno;
}
fclose(in);
}
}
closedir(dirp);
}

29
cpint.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* $Id: cpint.h,v 1.1 2006/08/11 19:53:07 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef __cpint_h
#define __cpint_h 1
extern void update_cpi_files(const char *dir,
void (*one_done_cb)(const char *date));
extern void combine_cpi_files(const char *dir, double *bests[], int *bestlen);
#endif /* __cpint_h */

42
doc/Makefile Normal file
View File

@@ -0,0 +1,42 @@
CONTENT=$(wildcard *.content)
HTML=$(subst .content,.html,$(CONTENT))
TARBALLS=$(wildcard gc_*.tgz)
OTHER=logo.png sample.gp sample.png cpint.gp cpint.png gui-preview.png
all: $(HTML)
.PHONY: all clean install
clean:
rm -f $(HTML)
install:
rsync -avz -e ssh $(HTML) $(TARBALLS) $(OTHER) \
srhea.net:public_html/goldencheetah/
# ftp -u ftp://srhea@ftp.goldencheetah.org/ $(HTML) $(TARBALLS) $(OTHER)
contact.html: contact.content genpage.pl
./genpage.pl "Contact Us" $< > $@
contrib.html: contrib.content genpage.pl
./genpage.pl "Contributors" $< > $@
download.html: download.content genpage.pl
./genpage.pl "Download" $< > $@
faq.html: faq.content genpage.pl
./genpage.pl "Frequently Asked Questions" $< > $@
index.html: index.content genpage.pl
./genpage.pl "Introduction" $< > $@
license.html: license.content genpage.pl
./genpage.pl "License" $< > $@
search.html: search.content genpage.pl
./genpage.pl "Search" $< > $@
users-guide.html: users-guide.content genpage.pl
./genpage.pl "User's Guide" $< > $@

5
doc/contact.content Normal file
View File

@@ -0,0 +1,5 @@
<!-- $Id: contact.content,v 1.1 2006/05/16 14:24:50 srhea Exp $ -->
Please send all correspondence to
<a href="mailto:info@goldencheetah.org">info@goldencheetah.org</a>.

18
doc/contrib.content Normal file
View File

@@ -0,0 +1,18 @@
<!-- $Id: contrib.content,v 1.4 2006/09/06 04:07:18 srhea Exp $ -->
Sean Rhea bought a PowerTap Pro on April 20, 2006, and immediately set to
figuring out how to use it from his Mac Powerbook without using Virtual PC.
Within a week, he was able to download the raw data. Shortly thereafter, Russ
Cox asked what he was up to, and the two worked together to figure out the
packing format used. By May 4, they could reproduce the numbers given by the
PowerTap software except for minor discrepancies in the time values. David
Easter then pointed out how the checksum bytes in the download protocol were
used, and Sean Rhea coded up their combined discoveries into the two
utilities, <code>ptdl</code> and <code>ptunpk</code>.
<p>
Rob Carlsen helped get the serial port version of the PowerTap Pro working
with the Keyspan USB-to-serial adaptor. Scott Overfield helped me figure out
that we should be using the <code>/dev/cu.*</code> devices instead of the
<code>/dev/tty.*</code> ones.

12
doc/cpint.gp Normal file
View File

@@ -0,0 +1,12 @@
set title "Critical Power Output"
set xlabel "Interval Duration (MM:SS)"
set ylabel "Average Power (Watts)"
set mxtics 5
set mytics 2
set yrange [0:]
set xrange [0.021:120]
set logscale x
set xtics ("0:01.26" 0.021, "0:05" 0.08333, "0:12" 0.2, "1:00" 1, "5:00" 5, \
"12:00" 12, "30:00" 30, "60:00" 60, "120:00" 120)
plot 'cpint.out' noti with li lt 1
pause -1

BIN
doc/cpint.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

43
doc/download.content Normal file
View File

@@ -0,0 +1,43 @@
<!-- $Id: download.content,v 1.6 2006/08/11 20:21:03 srhea Exp $ -->
Right now we're only distributing tarballs of the Golden Cheetah source.
<p>
<center>
<table width="100%">
<tr>
<td width="20%"><i>Date</i></td>
<td width="25%"><i>Source</i></td>
<td><i>Description</i></td>
</tr>
<tr>
<td valign="top">Aug 11, 2006</td>
<td valign="top"><a href="gc_2006-08-11.tgz">gc_2006-08-11.tgz</a></td>
<td valign="top">ptdl now works with Keyspan USB-to-serial adaptor, after
debugging help from Rob Carlsen.
</td>
</tr>
<tr>
<td valign="top">May 27, 2006</td>
<td valign="top"><a href="gc_2006-05-27.tgz">gc_2006-05-27.tgz</a></td>
<td valign="top">Adds the <code>cpint</code> program for computing critical
power intervals and the <code>ptpk</code> program for converting from
PowerTuned data files (see the <a href="users-guide.html">User's
Guide</a>).</td>
</tr>
<tr>
<td valign="top">May 16, 2006</td>
<td valign="top"><a href="gc_2006-05-16.tgz">gc_2006-05-16.tgz</a></td>
<td valign="top">The first code release, containing <code>ptdl</code> and
<code>ptunpk</code>.</td>
</tr>
</table>
</center>
<p>
We hope to have anonymous CVS access set up soon.

62
doc/faq.content Normal file
View File

@@ -0,0 +1,62 @@
<!-- $Id: faq.content,v 1.4 2006/07/05 16:59:56 srhea Exp $ -->
<p>
<i>Why does Golden Cheetah only include command-line utilities? Where's the
GUI?</i>
<p>
Because this project is only a hobby of ours, and we haven't had time to
finish it yet. That said, we are working on it. Here's a teaser screenshot
to keep you interested:
<center>
<img src="gui-preview.png" alt="GUI Screen Shot" align="center">
</center>
<p>
<i>Does the output of <code>ptunpk</code> exactly match that of the software
included with the PowerTap?</i>
<p>
Almost. If you run it in compatibility mode, using the <code>-c</code>
option, it matches the PowerTap software's output exactly on everything but
the time values, at least for the five sample rides we've tried it with.
The times are a little off, but not by more than 0.1%, so we don't consider it
a big deal.
<p>
That said, the PowerTap software does some weird things, like converting from
kilometers to miles by multiplying by 0.62, but then reporting the miles
values with five digits after the decimal place. If you run
<code>ptunpk</code> without the <code>-c</code> option, it will unpack the
data in the way we think it should. The results mostly match up with the
official ones, and are almost certainly identical within the range of accuracy
of the device.
<p>
<i>I've downloaded and unpacked the data. Now what do I do with it?</i>
<p>
We highly recommend that you buy and read both Joe Friel's <i>The
Cyclist's Training Bible</i> and Allen and Coggan's <i>Training and
Racing with a Power Meter</i>. The former is the definitive book about all
aspects of cycling training (although we think he's off his rocker in the
chapter about diet), and the latter is the definitive book about power-based
training.
<p>
Also, if you buy either of these books through the links below, we'll get a
referral fee in return, helping us to support this web site. Thanks!
<center>
<iframe
src="http://rcm.amazon.com/e/cm?t=goldencheetah-20&o=1&p=8&l=as1&asins=B0006JHZ7Q&fc1=000000&IS2=1&lt1=_blank&lc1=0000ff&bc1=000000&bg1=ffffff&f=ifr"
style="width:120px;height:240px;" scrolling="no" marginwidth="0"
marginheight="0" frameborder="0"></iframe>
<iframe
src="http://rcm.amazon.com/e/cm?t=goldencheetah-20&o=1&p=8&l=as1&asins=1931382794&fc1=000000&IS2=1&lt1=_blank&lc1=0000ff&bc1=000000&bg1=ffffff&f=ifr"
style="width:120px;height:240px;" scrolling="no" marginwidth="0"
marginheight="0" frameborder="0"></iframe>
</center>

BIN
doc/gc_2006-05-16.tgz Normal file

Binary file not shown.

BIN
doc/gc_2006-05-25.tgz Normal file

Binary file not shown.

BIN
doc/gc_2006-05-27.tgz Normal file

Binary file not shown.

BIN
doc/gc_2006-08-11.tgz Normal file

Binary file not shown.

131
doc/genpage.pl Executable file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/perl -w
#
# Copyright (c) 2001-2003 Regents of the University of California.
# All rights reserved.
#
# See the file LICENSE included in this distribution for details.
#
# $Id: genpage.pl,v 1.3 2006/07/05 16:59:56 srhea Exp $
use strict;
my $title = shift;
my $content_file = shift;
open (FILE, "$content_file") or die "Could not open $content_file";
print<<EOF;
<!--
Copyright (c) 2006 Sean C. Rhea (srhea\@srhea.net)
All rights reserved.
This file was automatically generated by genpage.pl. To change it,
please edit the content file, $content_file.
-->
<html>
<head>
<title>Golden Cheetah: Power for the Masses</title>
</head>
<body text="#000000"
link="#5e431b"
vlink="#996e2d"
alink="#000000"
bgcolor="#ffffff">
<table width="95%" border="0" width="100%" cellspacing="10">
<tr>
<!-- Left Column -->
<td width="150" valign="top">
<img src="logo.png" width="128" height="184" alt="Picture of Cheetah">
<p> <b><a href="index.html">Introduction</a></b>
<br> <b><a href="users-guide.html">User's Guide</a>
<br> <b><a href="faq.html">FAQ</a>
<br> <b><a href="license.html">License</a></b>
<br> <b><a href="download.html">Download</a></b>
<br> <b><a href="contrib.html">Contributors</a></b>
<br> <b><a href="search.html">Search</a></b>
<br> <b><a href="cgi-bin/mailman/listinfo/golden-cheetah-users">Mailing List</a></b>
<p>
<script type="text/javascript"><!--
google_ad_client = "pub-2993461533095312";
google_ad_width = 120;
google_ad_height = 240;
google_ad_format = "120x240_as";
google_ad_type = "text";
google_ad_channel ="";
google_color_border = "FFFFFF";
google_color_bg = "FFFFFF";
google_color_link = "5E431B";
google_color_url = "996E2D";
google_color_text = "000000";
//--></script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</td>
<!-- End of Left Column -->
<!-- Right Column -->
<td align="left" valign="top">
<table width="100%" cellspacing="10">
<tr align="center"><td>
<p>
<p>
<big><big><big><b><font face="arial,helvetica,sanserif">Golden
Cheetah</font></b></big></big></big>
<br>
<big><font face="arial,helvetica,sanserif">
Power for the Masses
</font></big>
<p>
</td></tr>
<tr><td bgcolor="#5e431b">
<font color="#f8d059" face="arial,helvetica,sanserif">
<big><strong>$title</strong></big>
</font>
</td></tr>
<tr><td>
EOF
my $match = "\\\$" . "Id:.* (\\d\\d\\d\\d\\/\\d\\d\\/\\d\\d "
. "\\d\\d:\\d\\d:\\d\\d) .*\\\$";
my $last_mod;
while (<FILE>) {
if (m/$match/) {
$last_mod = $1;
}
print;
}
close (FILE);
if (defined $last_mod) {
print "<p><hr><em>Last modified $last_mod.</em>\n";
}
print<<EOF;
</tr></td>
</table>
</td>
<!-- End of Right Column -->
</tr>
</table>
</body>
</html>
EOF

BIN
doc/gui-preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

26
doc/index.content Normal file
View File

@@ -0,0 +1,26 @@
<!-- $Id: index.content,v 1.1 2006/05/16 14:24:50 srhea Exp $ -->
<p>
The goal of the Golden Cheetah project is to develop a software package that:
<ul>
<li>Downloads ride data from power measurement devices, such as the <a
href="http://www.cycleops.com/products/powertap.htm">CycleOps PowerTap</a>,
the <a href="http://www.ergomo.net/Home-_14.html">ergomo</a>, the <a
href="http://www.polarusa.com/consumer/powerkit/default.asp">Polar
Electro</a>, and the <a href="http://www.srm.de/usa/index.html">SRM Training
System</a><p>
<li>Helps athletes analyze downloaded data with features akin to commercial
power analysis software, such as <a href="http://cyclingpeaks.com/">Cycling
Peaks</a><p>
<li>Works on non-Microsoft Windows-based systems, such as FreeBSD, Linux, and
Mac OS X<p>
<li>Is available under an
<a href="http://www.opensource.org/docs/definition.php">Open Source</a>
license
</ul>
<p>
In short, we believe that cyclists should be able to download their power data
to the computer of their choice, analyze it in whatever way they see fit, and
share their methods of analysis with others.

68
doc/license.content Normal file
View File

@@ -0,0 +1,68 @@
<!-- $Id: license.content,v 1.1 2006/05/16 14:24:50 srhea Exp $ -->
Golden Cheetah is licensed under the
<a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License</a>,
the preamble of which states:
<blockquote>
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
</P>
<P>
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
</P>
<P>
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
</P>
<P>
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
</P>
<P>
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
</P>
<P>
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
</P>
<P>
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
</blockquote>
For the full text of the license, please click the link above.

BIN
doc/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
doc/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

8
doc/sample.gp Normal file
View File

@@ -0,0 +1,8 @@
set title "May 3, 2006 16:24:04"
set xlabel "Ride Time (minutes)"
plot \
"< awk '/^[^#]/ {print $1, $4}' 2006_05_03_16_24_04.dat | ./smooth.pl 10" ti 'Watts' wi li, \
"< awk '/^[^#]/ {print $1, $7}' 2006_05_03_16_24_04.dat | ./smooth.pl 10" ti 'Heartrate' wi li, \
"< awk '/^[^#]/ {print $1, $6}' 2006_05_03_16_24_04.dat | ./smooth.pl 10" ti 'Cadence' wi li, \
"< awk '/^[^#]/ {print $1, $3}' 2006_05_03_16_24_04.dat | ./smooth.pl 10" ti 'MPH' wi li
pause -1

BIN
doc/sample.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

42
doc/search.content Normal file
View File

@@ -0,0 +1,42 @@
<!-- $Id: search.content,v 1.1 2006/05/16 15:46:42 srhea Exp $ -->
<center>
<!-- SiteSearch Google -->
<form method="get" action="http://www.google.com/custom" target="_top">
<table border="0" bgcolor="#ffffff">
<tr><td nowrap="nowrap" valign="top" align="left" height="32">
<a href="http://www.google.com/">
<img src="http://www.google.com/logos/Logo_25wht.gif" border="0" alt="Google"
align="middle"></img></a>
<br/>
<input type="hidden" name="domains" value="goldencheetah.org"></input>
<input type="text" name="q" size="60" maxlength="255" value=""></input>
<input type="submit" name="sa" value="Search"></input>
</td></tr>
<tr>
<td nowrap="nowrap">
<table>
<tr>
<td>
<input type="radio" name="sitesearch" value="" checked="checked"></input>
<font size="-1" color="#000000">Web</font>
</td>
<td>
<input type="radio" name="sitesearch" value="goldencheetah.org"></input>
<font size="-1" color="#000000">goldencheetah.org</font>
</td>
</tr>
</table>
<input type="hidden" name="client" value="pub-2993461533095312"></input>
<input type="hidden" name="forid" value="1"></input>
<input type="hidden" name="ie" value="ISO-8859-1"></input>
<input type="hidden" name="oe" value="ISO-8859-1"></input>
<input type="hidden" name="safe" value="active"></input>
<input type="hidden" name="cof"
value="GALT:#008000;GL:1;DIV:#336699;VLC:663399;AH:center;BGC:FFFFFF;LBGC:336699;ALC:0000FF;LC:0000FF;T:000000;GFNT:0000FF;GIMP:0000FF;FORID:1;"></input>
<input type="hidden" name="hl" value="en"></input>
</td></tr></table>
</form>
<!-- SiteSearch Google -->
</center>

222
doc/users-guide.content Normal file
View File

@@ -0,0 +1,222 @@
<!-- $Id: users-guide.content,v 1.5 2006/05/27 16:32:46 srhea Exp $ -->
Currently, Golden Cheetah consists of several command line utilities:
<code>ptdl</code>, which downloads ride data from a PowerTap Pro version 2.21
cycling computer, <code>ptunpk</code>, which unpacks the raw bytes downloaded
by <code>ptdl</code> and outputs more human-friendly ride information, and
<code>cpint</code>, which computes your critical power (see below). All three
are written in simple C code but have only been tested on Mac OS X so far.
We've also written several Perl scripts to help you graph and summarize the
data.
<p>
<big><font face="arial,helvetica,sanserif">
Extracting the Data
</font></big>
<p>
To use <code>ptdl</code>, you'll first need to install
<a href="http://www.ftdichip.com/Drivers/VCP.htm">the drivers</a> for the
FTDI chip the PowerTap Pro USB Downloader uses. Once these are installed, you
should be able to just run <code>ptdl</code> without arguments:
<pre>
$ ./ptdl
Reading from /dev/tty.usbserial-3B1.
Reading version information...done.
Reading ride time...done.
Writing to 2006_05_15_11_34_03.raw.
Reading ride data..............done.
$ head -5 2006_05_15_11_34_03.raw
57 56 55 64 02 15
60 06 05 0f 6b 22
40 08 30 00 00 00
86 0e 74 99 00 55
81 06 77 a8 40 55
</pre>
<p>
If everything goes well, <code>ptdl</code> will automatically detect the
device (<code>/dev/tty.usbserial-3B1</code> in the example above), read the
ride data from it, and write to a file named by the date and time at which the
ride started (<code>2006_05_15_11_34_03.raw</code> in the example; the format
is YYYY_MM_DD_hh_mm_ss.raw).
<p>
<big><font face="arial,helvetica,sanserif">
Unpacking the Data
</font></big>
<p>As shown by the <code>head</code> command above, the data in this
<code>.raw</code> file is just the raw bytes that represent your ride. To
unpack those bytes and display them in a more human-friendly format, use
<code>ptunpk</code>:
<pre>
$ ./ptunpk 2006_05_15_11_34_03.raw
$ head -5 2006_05_15_11_34_03.dat
# Time Torq MPH Watts Miles Cad HR Int
# 2006/5/15 11:34:03 1147707243
# wheel size=2096 mm, interval=0, rec int=1
0.021 13.1 2.450 43 0.00781 0 85 0
0.042 13.4 5.374 97 0.00912 64 85 0
</pre>
<code>ptunpk</code> takes a <code>.raw</code> file for input and writes a
<code>.dat</code> file as output. Lines that start with an ampersand ("#") in
this file are comments; the other lines represent measured samples. As shown
by the first comment in the file, the columns are: time in minutes, torque in
Newton-meters, speed in miles per hour, power in watts, distance in miles,
cadence, heart rate, and interval number.
<p>
<big><font face="arial,helvetica,sanserif">
Summarizing the Data
</font></big>
<p>
We hope to have a graphical interface to these programs soon, but until then,
the only summarization tools we have are command-line programs. The script
<code>intervals.pl</code> summarizes the intervals performed in a workout:
<small>
<pre>
$ ./intervals.pl 2006_05_03_16_24_04.dat
Power Heart Rate Cadence Speed
Int Dur Dist Avg Max Avg Max Avg Max Avg Max
0 77:10 19.3 213 693 134 167 82 141 16.0 27.8
1 4:03 0.9 433 728 175 203 84 122 13.0 18.8
2 7:23 1.0 86 502 135 179 71 141 16.0 28.2
3 4:27 0.9 390 628 170 181 70 100 12.0 17.6
4 8:04 0.9 60 203 130 178 50 120 18.0 30.1
5 4:30 0.9 384 682 170 179 79 113 11.0 18.6
6 8:51 1.1 53 245 125 176 70 141 8.0 26.6
7 2:48 0.4 400 614 164 178 62 91 8.0 13.6
8 7:01 1.1 46 268 128 170 71 141 12.0 28.8
9 4:30 0.9 379 560 168 180 81 170 11.0 18.3
10 28:46 6.5 120 409 128 179 79 141 15.0 31.0
</pre>
</small>
<p>
In the example above, a rider performed five hill intervals, four of which
climbed a medium size hill that took about 4-5 minutes to climb (intervals
1, 3, 5, and 9), and one on a shorter hill that took just under 3 minutes to
climb (interval 7).
<p>
<big><font face="arial,helvetica,sanserif">
Graphing the Data
</font></big>
<p>
For graphing the data in the ride, we use <code>smooth.pl</code> and the
<code>gnuplot</code> program. You can use <a href="sample.gp">sample.gp</a>
to graph the power, heart rate, cadence, and speed for the hill workout above:
<pre>
$ gnuplot sample.gp
</pre>
<img align="center" alt="Sample Plot" src="sample.png">
<p>
<big><font face="arial,helvetica,sanserif">
Finding Your "Critical Power"
</font></big>
<p>
Joe Friel calls the maximum average power a rider can sustain over an interval
the rider's "critical power" for that duration. The <code>cpint</code>
program automatically computes your critical power over all interval lengths
using the data from all your past rides. This program looks at all the
<code>.raw</code> files in a directory, calculating your maximum power over
every subinterval length and storing them in a corresponding <code>.cpi</code>
file. It then combines the data in all of the <code>.cpi</code> files to find
your critical power over <i>all</i> subintervals of <i>all</i> your rides.
<pre>
$ ls *.raw
2006_04_28_10_48_33.raw 2006_05_10_17_08_30.raw 2006_05_18_16_32_53.raw
2006_05_03_16_24_04.raw 2006_05_13_10_29_12.raw 2006_05_21_12_25_07.raw
2006_05_05_10_52_05.raw 2006_05_15_11_34_03.raw 2006_05_22_18_28_47.raw
...
2006_05_09_09_54_29.raw 2006_05_17_16_44_35.raw
$ ./cpint
Compiling data for ride on Fri Apr 28 10:48:33 2006...done.
Compiling data for ride on Sat Apr 29 10:07:48 2006...done.
Compiling data for ride on Sun Apr 30 14:00:17 2006...done.
...
Compiling data for ride on Mon May 22 18:28:47 2006...done.
0.021 1264
0.042 1221
0.063 1216
...
5.019 391
...
171.885 163
</pre>
<p>
Over this set of rides, the rider's maximum power is 1264 watts, achieved over
an interval of 0.021 minutes (1.26 seconds). Over all five-minute
subintervals, he has achieved a maximum average power of 391 watts. The
longest ride in this set was 171.885 minutes long, and he averaged 163 watts
over it.
<p>
We can graph the output of <code>cpint</code> using <code>gnuplot</code> with
<a href="cpint.gp">cpint.gp</a>:
<pre>
$ ./cpint > cpint.out
$ gnuplot cpint.gp
</pre>
<img src="cpint.png">
<p>
The first time you run <code>cpint</code> it will take a while, as it has to
analyze all your past rides. On subsequent runs, however, it will only
analyze new files.
<p><i>Training and Racing with a Power Meter</i> (see the <a
href="faq.html">FAQ</a>) contains a table of critical powers of Cat 5 cyclists
up through international pros at interval lengths of 5 seconds, 1 minute, 5
minutes, and 60 minutes. Using this table and the <code>cpint</code> program,
you can determine whether you're stronger than others in your racing category
at each interval length and adapt your training program accordingly.
<p>
<big><font face="arial,helvetica,sanserif">
Converting Old Data
</font></big>
<p>
If you've used the PowerTuned software that comes with the PowerTap you may
have lots of old ride data in that program that you'd like to include in your
critical power graph. You can convert the <code>.xml</code> files that
PowerTuned produces to <code>.raw</code> files using the <code>ptpk</code>
program:
<p>
<pre>
$ ./ptpk 2006_04_27_00_23_28.xml
$ head -5 2006_04_27_00_23_28.raw
57 56 55 64 02 15
60 06 04 7b 80 17
40 08 30 00 00 00
84 04 00 24 00 ff
83 03 00 d7 00 ff
</pre>
<p>
<code>ptpk</code> assumes the input <code>.xml</code> file was generated with
a wheel size of 2,096 mm and a recording interval of 1. If this is not the
case, you should specify the correct values with the <code>-w</code> and
<code>-r</code> options.
<p>
Note that the PowerTuned software computes the output speed in miles per hour
by multiplying the measured speed in kilometers per hour by 0.62, and the
miles per hour values in a <code>.xml</code> file are thus only accurate to
two significant figures, even though they're printed out to three decimal
places. Because of this limitation, the sequence <code>ptpk</code>,
<code>ptunpk</code> is not quite the identity function; in particular, the
wattage values from <code>ptpk</code> may only be accurate to two significant
digits.

176
gui/AllPlot.cpp Normal file
View File

@@ -0,0 +1,176 @@
/*
* $Id: AllPlot.cpp,v 1.2 2006/07/12 02:13:57 srhea Exp $
*
* Copyright (c) 2006 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 "AllPlot.h"
#include "RawFile.h"
#include "Settings.h"
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_legend.h>
#include <qwt_data.h>
static const inline double
max(double a, double b) { if (a > b) return a; else return b; }
AllPlot::AllPlot() :
hrArray(NULL), wattsArray(NULL), timeArray(NULL), smooth(30)
{
insertLegend(new QwtLegend(), QwtPlot::BottomLegend);
setCanvasBackground(Qt::white);
setAxisTitle(xBottom, "Time (minutes)");
setAxisTitle(yLeft, "Power / HR");
wattsCurve = new QwtPlotCurve("Power");
wattsCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
wattsCurve->setPen(QPen(Qt::red));
wattsCurve->attach(this);
hrCurve = new QwtPlotCurve("Heart Rate");
hrCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
hrCurve->setPen(QPen(Qt::blue));
hrCurve->attach(this);
grid = new QwtPlotGrid();
grid->enableX(false);
QPen gridPen;
gridPen.setStyle(Qt::DotLine);
grid->setPen(gridPen);
grid->attach(this);
}
struct DataPoint {
double time, hr, watts;
DataPoint(double t, double h, double w) : time(t), hr(h), watts(w) {}
};
void
AllPlot::recalc()
{
int rideTimeSecs = (int) ceil(timeArray[arrayLength - 1]);
double ymax = 0.0;
double totalWatts = 0.0;
double totalHr = 0.0;
QList<DataPoint*> list;
int i = 0;
double *smoothWatts = new double[rideTimeSecs + 1];
double *smoothHr = new double[rideTimeSecs + 1];
double *smoothTime = new double[rideTimeSecs + 1];
for (int secs = 0; secs < smooth; ++secs) {
smoothWatts[secs] = 0.0;
smoothHr[secs] = 0.0;
smoothTime[secs] = secs / 60.0;
}
for (int secs = smooth; secs <= rideTimeSecs; ++secs) {
while ((i < arrayLength) && (timeArray[i] <= secs)) {
DataPoint *dp =
new DataPoint(timeArray[i], hrArray[i], wattsArray[i]);
totalWatts += wattsArray[i];
totalHr += hrArray[i];
list.append(dp);
++i;
}
while (!list.empty() && (list.front()->time < secs - smooth)) {
DataPoint *dp = list.front();
list.removeFirst();
totalWatts -= dp->watts;
totalHr -= dp->hr;
delete dp;
}
// TODO: this is wrong. We should do a weighted average over the
// seconds represented by each point...
if (list.empty()) {
smoothWatts[secs] = 0.0;
smoothHr[secs] = 0.0;
}
else {
smoothWatts[secs] = totalWatts / list.size();
if (smoothWatts[secs] > ymax)
ymax = smoothWatts[secs];
smoothHr[secs] = totalHr / list.size();
if (smoothHr[secs] > ymax)
ymax = smoothHr[secs];
}
smoothTime[secs] = secs / 60.0;
}
wattsCurve->setData(smoothTime, smoothWatts, rideTimeSecs + 1);
hrCurve->setData(smoothTime, smoothHr, rideTimeSecs + 1);
setAxisScale(xBottom, 0.0, smoothTime[rideTimeSecs]);
setAxisScale(yLeft, 0.0, ymax + 30);
replot();
delete [] smoothWatts;
delete [] smoothHr;
delete [] smoothTime;
}
void
AllPlot::setData(RawFile *raw)
{
delete [] hrArray;
delete [] wattsArray;
delete [] timeArray;
setTitle(raw->startTime.toString(GC_DATETIME_FORMAT));
hrArray = new double[raw->points.size()];
wattsArray = new double[raw->points.size()];
timeArray = new double[raw->points.size()];
arrayLength = 0;
QListIterator<RawFilePoint*> i(raw->points);
while (i.hasNext()) {
RawFilePoint *point = i.next();
timeArray[arrayLength] = point->secs;
hrArray[arrayLength] = max(0, point->hr);
wattsArray[arrayLength] = max(0, point->watts);
++arrayLength;
}
recalc();
}
void
AllPlot::showPower(int state)
{
assert(state != Qt::PartiallyChecked);
wattsCurve->setVisible(state == Qt::Checked);
replot();
}
void
AllPlot::showHr(int state)
{
assert(state != Qt::PartiallyChecked);
hrCurve->setVisible(state == Qt::Checked);
replot();
}
void
AllPlot::showGrid(int state)
{
assert(state != Qt::PartiallyChecked);
grid->setVisible(state == Qt::Checked);
replot();
}
void
AllPlot::setSmoothing(int value)
{
smooth = value;
recalc();
}

67
gui/AllPlot.h Normal file
View File

@@ -0,0 +1,67 @@
/*
* $Id: AllPlot.h,v 1.2 2006/07/12 02:13:57 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef _GC_AllPlot_h
#define _GC_AllPlot_h 1
#include <qwt_plot.h>
class QwtPlotCurve;
class QwtPlotGrid;
class RawFile;
class AllPlot : public QwtPlot
{
Q_OBJECT
public:
QwtPlotCurve *wattsCurve;
QwtPlotCurve *hrCurve;
AllPlot();
int smoothing() const { return smooth; }
void setData(RawFile *raw);
public slots:
void showPower(int state);
void showHr(int state);
void showGrid(int state);
void setSmoothing(int value);
protected:
QwtPlotGrid *grid;
double *hrArray;
double *wattsArray;
double *timeArray;
int arrayLength;
int smooth;
void recalc();
};
#endif // _GC_AllPlot_h

112
gui/ChooseCyclistDialog.cpp Normal file
View File

@@ -0,0 +1,112 @@
/*
* $Id: ChooseCyclistDialog.cpp,v 1.3 2006/07/04 12:55:40 srhea Exp $
*
* Copyright (c) 2006 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 "ChooseCyclistDialog.h"
#include <QtGui>
ChooseCyclistDialog::ChooseCyclistDialog(const QDir &home, bool allowNew) :
home(home)
{
setWindowTitle("Choose a Cyclist");
listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
QStringListIterator i(home.entryList(QDir::Dirs | QDir::NoDotAndDotDot));
while (i.hasNext()) {
QString name = i.next();
new QListWidgetItem(name, listWidget);
}
if (allowNew)
newButton = new QPushButton(tr("&New..."), this);
okButton = new QPushButton(tr("&Open"), this);
cancelButton = new QPushButton(tr("&Cancel"), this);
okButton->setEnabled(false);
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
if (allowNew)
connect(newButton, SIGNAL(clicked()), this, SLOT(newClicked()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
connect(listWidget,
SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
this, SLOT(enableOk(QListWidgetItem*)));
connect(listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
this, SLOT(accept()));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(okButton);
if (allowNew)
buttonLayout->addWidget(newButton);
buttonLayout->addWidget(cancelButton);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(listWidget);
mainLayout->addLayout(buttonLayout);
}
QString
ChooseCyclistDialog::choice()
{
return listWidget->currentItem()->text();
}
void
ChooseCyclistDialog::enableOk(QListWidgetItem *item)
{
okButton->setEnabled(item != NULL);
}
void
ChooseCyclistDialog::cancelClicked()
{
reject();
}
QString
ChooseCyclistDialog::newCyclistDialog(QDir &homeDir, QWidget *parent)
{
QDir home(homeDir);
bool ok;
QString name = QInputDialog::getText(parent, tr("Create New Cyclist"),
tr("Enter New Cyclist's Name"),
QLineEdit::Normal, "", &ok);
if (ok && !name.isEmpty()) {
fprintf(stderr, "New name: %s\n", name.toAscii().data());
if (!home.exists(name)) {
if (home.mkdir(name))
return name;
QMessageBox::critical(0, tr("Fatal Error"),
tr("Can't create new directory ")
+ home.path() + "/" + name, "OK");
}
}
return QString();
}
void
ChooseCyclistDialog::newClicked()
{
QString name = newCyclistDialog(home, this);
if (!name.isEmpty())
new QListWidgetItem(name, listWidget);
}

53
gui/ChooseCyclistDialog.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* $Id: ChooseCyclistDialog.h,v 1.3 2006/07/04 12:55:40 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef _GC_ChooseCyclistDialog_h
#define _GC_ChooseCyclistDialog_h 1
#include <QDialog>
#include <QDir>
class QListWidget;
class QListWidgetItem;
class QPushButton;
class ChooseCyclistDialog : public QDialog
{
Q_OBJECT
public:
ChooseCyclistDialog(const QDir &home, bool allowNew);
QString choice();
static QString newCyclistDialog(QDir &homeDir, QWidget *parent);
private slots:
void newClicked();
void cancelClicked();
void enableOk(QListWidgetItem *item);
private:
QDir home;
QListWidget *listWidget;
QPushButton *okButton, *newButton, *cancelButton;
};
#endif // _GC_ChooseCyclistDialog_h

312
gui/DownloadRideDialog.cpp Normal file
View File

@@ -0,0 +1,312 @@
/*
* $Id: DownloadRideDialog.cpp,v 1.4 2006/08/11 20:02:13 srhea Exp $
*
* Copyright (c) 2006 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 "DownloadRideDialog.h"
#include "MainWindow.h"
#include <QtGui>
#include <errno.h>
#include <fcntl.h>
#define MAX_DEVICES 10
DownloadRideDialog::DownloadRideDialog(MainWindow *mainWindow,
const QDir &home) :
mainWindow(mainWindow), home(home), fd(-1), out(NULL), device(NULL),
notifier(NULL), timer(NULL), blockCount(0)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle("Download Ride Data");
listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
QLabel *availLabel = new QLabel(tr("Available devices:"), this);
QLabel *instructLabel = new QLabel(tr("Instructions:"), this);
label = new QLabel(this);
label->setIndent(10);
downloadButton = new QPushButton(tr("&Download"), this);
rescanButton = new QPushButton(tr("&Rescan"), this);
cancelButton = new QPushButton(tr("&Cancel"), this);
connect(downloadButton, SIGNAL(clicked()), this, SLOT(downloadClicked()));
connect(rescanButton, SIGNAL(clicked()), this, SLOT(scanDevices()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
connect(listWidget,
SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
this, SLOT(setReadyInstruct()));
connect(listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
this, SLOT(downloadClicked()));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(downloadButton);
buttonLayout->addWidget(rescanButton);
buttonLayout->addWidget(cancelButton);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(availLabel);
mainLayout->addWidget(listWidget);
mainLayout->addWidget(instructLabel);
mainLayout->addWidget(label);
mainLayout->addLayout(buttonLayout);
scanDevices();
}
DownloadRideDialog::~DownloadRideDialog()
{
if (device)
free(device);
if (fd >= 0)
::close(fd);
if (out)
fclose(out);
}
void
DownloadRideDialog::setReadyInstruct()
{
label->setText(tr("Make sure the PowerTap unit is turned on,\n"
"and that the screen display says, \"Host\",\n"
"then click Download to begin downloading."));
}
void
DownloadRideDialog::scanDevices()
{
listWidget->clear();
char *devices[MAX_DEVICES];
int devcnt = pt_find_device(devices, MAX_DEVICES);
for (int i = 0; i < devcnt; ++i) {
new QListWidgetItem(devices[i], listWidget);
free(devices[i]);
}
if (listWidget->count() == 1) {
listWidget->setCurrentRow(0);
setReadyInstruct();
downloadButton->setEnabled(true);
downloadButton->setFocus();
}
else {
downloadButton->setEnabled(false);
if (listWidget->count() > 1) {
label->setText(tr("Select the device from the above list from\n"
"which you would like to download a ride."));
}
else {
label->setText(tr("No devices found. Make sure the PowerTap\n"
"unit is plugged into the computer's USB port,\n"
"then click \"Rescan\" to check again."));
}
}
}
static void
time_cb(struct tm *time, void *self)
{
((DownloadRideDialog*) self)->time_cb(time);
}
static void
record_cb(unsigned char *buf, void *self)
{
((DownloadRideDialog*) self)->record_cb(buf);
}
void
DownloadRideDialog::time_cb(struct tm *time)
{
timer->stop();
if (!out) {
if (!time) {
QMessageBox::critical(this, tr("Read error"),
tr("Can't find ride time"));
reject();
}
sprintf(outname, "%04d_%02d_%02d_%02d_%02d_%02d.raw",
time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec);
assert(strlen(outname) == sizeof(outname) - 1);
label->setText(label->text() + tr("done.\nWriting to ")
+ outname + ".");
std::string path = home.absolutePath().toStdString() + "/" + outname;
if ((out = fopen(path.c_str(), "r")) != NULL) {
if (QMessageBox::warning(this,
tr("Ride Already Downloaded"),
tr("This ride appears to have already ")
+ tr("been downloaded. Do you want to ")
+ tr("download it again and overwrite ")
+ tr("the previous download?"),
tr("&Overwrite"), tr("&Cancel"),
QString(), 1, 1) == 1) {
reject();
}
}
if ((out = fopen(path.c_str(), "w")) == NULL) {
QMessageBox::critical(this, tr("Write error"),
tr("Can't open ") + path.c_str()
+ tr(" for writing: ") + strerror(errno));
reject();
}
label->setText(label->text() + tr("\nReading ride data..."));
}
timer->start(5000);
}
void
DownloadRideDialog::record_cb(unsigned char *buf)
{
timer->stop();
if (!out) {
QMessageBox::critical(this, tr("Read error"),
tr("Can't find ride time."));
reject();
}
for (int i = 0; i < 6; ++i)
fprintf(out, "%02x%s", buf[i], (i == 5) ? "\n" : " ");
if ((++blockCount % 256) == 0) {
label->setText(label->text() + ".");
}
timer->start(5000);
}
void
DownloadRideDialog::readVersion()
{
if (notifier)
notifier->setEnabled(false);
int r = pt_read_version(&vstate, fd, hwecho);
if (r == PT_DONE) {
if (notifier) {
delete notifier;
notifier = NULL;
}
if (timer) {
delete timer;
timer = NULL;
}
label->setText(label->text() + tr("done."));
::close(fd);
fd = open(device, O_RDWR | O_NOCTTY);
if (fd < 0) {
QMessageBox::critical(this, tr("Read error"),
tr("Could not open device, ") + device +
+ ": " + strerror(errno));
reject();
}
pt_make_async(fd);
label->setText(label->text() + tr("\nReading ride time..."));
memset(&dstate, 0, sizeof(dstate));
readData();
}
else {
assert(r == PT_NEED_READ);
if (notifier)
notifier->setEnabled(true);
else {
notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
connect(notifier, SIGNAL(activated(int)), this, SLOT(readVersion()));
}
if (!timer) {
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(versionTimeout()));
timer->start(5000);
}
}
}
void
DownloadRideDialog::versionTimeout()
{
timer->stop();
label->setText(label->text() + tr("timeout."));
QMessageBox::critical(this, tr("Read Error"),
tr("Timed out reading device ") + device
+ tr(". Check that the device is plugged in and ")
+ tr("try again."));
reject();
}
void
DownloadRideDialog::readData()
{
if (notifier)
notifier->setEnabled(false);
int r = pt_read_data(&dstate, fd, hwecho, ::time_cb, ::record_cb, this);
if (r == PT_DONE) {
if (notifier) {
delete notifier;
notifier = NULL;
}
if (timer) {
delete timer;
timer = NULL;
}
label->setText(label->text() + tr("done."));
QMessageBox::information(this, tr("Success"), tr("Download complete."));
fclose(out);
out = NULL;
mainWindow->addRide(outname);
accept();
}
else {
assert(r == PT_NEED_READ);
if (notifier)
notifier->setEnabled(true);
else {
notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
connect(notifier, SIGNAL(activated(int)), this, SLOT(readData()));
}
if (!timer) {
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(versionTimeout()));
timer->start(5000);
}
}
}
void
DownloadRideDialog::downloadClicked()
{
downloadButton->setEnabled(false);
rescanButton->setEnabled(false);
if (device)
free(device);
device = strdup(listWidget->currentItem()->text().toAscii().data());
hwecho = pt_hwecho(device);
fd = open(device, O_RDWR | O_NOCTTY);
if (fd < 0) {
QMessageBox::critical(this, tr("Read error"),
tr("Could not open device, ") + device +
+ ": " + strerror(errno));
reject();
}
pt_make_async(fd);
label->setText(tr("Reading version information..."));
memset(&vstate, 0, sizeof(vstate));
readVersion();
}
void
DownloadRideDialog::cancelClicked()
{
reject();
}

72
gui/DownloadRideDialog.h Normal file
View File

@@ -0,0 +1,72 @@
/*
* $Id: DownloadRideDialog.h,v 1.4 2006/08/11 20:02:13 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef _GC_DownloadRideDialog_h
#define _GC_DownloadRideDialog_h 1
#include <QtGui>
extern "C" {
#include "../pt.h"
}
class MainWindow;
class DownloadRideDialog : public QDialog
{
Q_OBJECT
public:
DownloadRideDialog(MainWindow *mainWindow, const QDir &home);
~DownloadRideDialog();
void time_cb(struct tm *time);
void record_cb(unsigned char *buf);
private slots:
void downloadClicked();
void cancelClicked();
void setReadyInstruct();
void scanDevices();
void readVersion();
void readData();
void versionTimeout();
private:
MainWindow *mainWindow;
QDir home;
QListWidget *listWidget;
QPushButton *downloadButton, *rescanButton, *cancelButton;
QLabel *label;
int fd;
FILE *out;
char outname[24];
char *device;
struct pt_read_version_state vstate;
struct pt_read_data_state dstate;
QSocketNotifier *notifier;
QTimer *timer;
int blockCount;
int hwecho;
};
#endif // _GC_DownloadRideDialog_h

30
gui/GoldenCheetah.pro Normal file
View File

@@ -0,0 +1,30 @@
######################################################################
# Automatically generated by qmake (2.00a) Sun May 28 19:43:48 2006
######################################################################
TEMPLATE = app
TARGET +=
DEPENDPATH += .
INCLUDEPATH += ../qwt-20060130/include
CONFIG += static
OBJECTS += ../pt.o
LIBS += /usr/local/lib/libqwt.a
LIBS += -lz -framework Carbon
# Input
HEADERS += \
AllPlot.h \
ChooseCyclistDialog.h \
DownloadRideDialog.h \
MainWindow.h \
RawFile.h \
RideItem.h
SOURCES += \
AllPlot.cpp \
ChooseCyclistDialog.cpp \
DownloadRideDialog.cpp \
MainWindow.cpp \
RawFile.cpp \
RideItem.cpp \
\
main.cpp

279
gui/MainWindow.cpp Normal file
View File

@@ -0,0 +1,279 @@
/*
* $Id: MainWindow.cpp,v 1.7 2006/07/12 02:13:57 srhea Exp $
*
* Copyright (c) 2006 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 "MainWindow.h"
#include "AllPlot.h"
#include "ChooseCyclistDialog.h"
#include "DownloadRideDialog.h"
#include "RawFile.h"
#include "RideItem.h"
#include "Settings.h"
#include <QApplication>
#include <QtGui>
#include <QRegExp>
#define FOLDER_TYPE 0
#define RIDE_TYPE 1
static char *rideFileRegExp = ("^(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)"
"_(\\d\\d)_(\\d\\d)_(\\d\\d)\\.raw$");
MainWindow::MainWindow(const QDir &home) :
home(home), settings(GC_SETTINGS_CO, GC_SETTINGS_APP)
{
setWindowTitle(home.dirName());
settings.setValue(GC_SETTINGS_LAST, home.dirName());
QVariant geom = settings.value(GC_SETTINGS_MAIN_GEOM);
if (geom == QVariant())
resize(640, 480);
else
setGeometry(geom.toRect());
splitter = new QSplitter(this);
setCentralWidget(splitter);
treeWidget = new QTreeWidget;
treeWidget->setColumnCount(3);
treeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
// TODO:
// treeWidget->header()->resizeSection(0,80);
treeWidget->header()->hide();
allRides = new QTreeWidgetItem(treeWidget, FOLDER_TYPE);
allRides->setText(0, tr("All Rides"));
treeWidget->expandItem(allRides);
splitter->addWidget(treeWidget);
QRegExp rx(rideFileRegExp);
QStringList filters;
filters << "*.raw";
QTreeWidgetItem *last = NULL;
QStringListIterator i(home.entryList(filters, QDir::Files));
while (i.hasNext()) {
QString name = i.next();
if (rx.exactMatch(name)) {
assert(rx.numCaptures() == 6);
QDate date(rx.cap(1).toInt(), rx.cap(2).toInt(),rx.cap(3).toInt());
QTime time(rx.cap(4).toInt(), rx.cap(5).toInt(),rx.cap(6).toInt());
QDateTime dt(date, time);
last = new RideItem(allRides, RIDE_TYPE, home.path(), name, dt);
}
}
tabWidget = new QTabWidget;
rideSummary = new QTextEdit;
rideSummary->setReadOnly(true);
tabWidget->addTab(rideSummary, "Ride Summary");
QWidget *window = new QWidget;
QVBoxLayout *vlayout = new QVBoxLayout;
QHBoxLayout *showLayout = new QHBoxLayout;
QLabel *showLabel = new QLabel("Show:", this);
showLayout->addWidget(showLabel);
QCheckBox *showGrid = new QCheckBox("Grid", this);
showGrid->setCheckState(Qt::Checked);
showLayout->addWidget(showGrid);
QCheckBox *showPower = new QCheckBox("Power", this);
showPower->setCheckState(Qt::Checked);
showLayout->addWidget(showPower);
QCheckBox *showHr = new QCheckBox("Heart Rate", this);
showHr->setCheckState(Qt::Checked);
showLayout->addWidget(showHr);
QHBoxLayout *smoothLayout = new QHBoxLayout;
QLabel *smoothLabel = new QLabel(tr("Smoothing (secs)"), this);
smoothLineEdit = new QLineEdit(this);
smoothLineEdit->setFixedWidth(30);
smoothLayout->addWidget(smoothLabel);
smoothLayout->addWidget(smoothLineEdit);
smoothSlider = new QSlider(Qt::Horizontal);
smoothSlider->setTickPosition(QSlider::TicksBelow);
smoothSlider->setTickInterval(1);
smoothSlider->setMinimum(2);
smoothSlider->setMaximum(60);
smoothLineEdit->setValidator(new QIntValidator(smoothSlider->minimum(),
smoothSlider->maximum(),
smoothLineEdit));
smoothLayout->addWidget(smoothSlider);
allPlot = new AllPlot;
smoothSlider->setValue(allPlot->smoothing());
smoothLineEdit->setText(QString("%1").arg(allPlot->smoothing()));
vlayout->addWidget(allPlot);
vlayout->addLayout(showLayout);
vlayout->addLayout(smoothLayout);
window->setLayout(vlayout);
window->show();
tabWidget->addTab(window, "All-in-One Graph");
splitter->addWidget(tabWidget);
QVariant splitterSizes = settings.value(GC_SETTINGS_SPLITTER_SIZES);
if (splitterSizes != QVariant())
splitter->restoreState(splitterSizes.toByteArray());
else {
QList<int> sizes;
sizes.append(250);
sizes.append(390);
splitter->setSizes(sizes);
}
connect(treeWidget, SIGNAL(itemSelectionChanged()),
this, SLOT(rideSelected()));
connect(splitter, SIGNAL(splitterMoved(int,int)),
this, SLOT(splitterMoved()));
connect(showPower, SIGNAL(stateChanged(int)),
allPlot, SLOT(showPower(int)));
connect(showHr, SIGNAL(stateChanged(int)),
allPlot, SLOT(showHr(int)));
connect(showGrid, SIGNAL(stateChanged(int)),
allPlot, SLOT(showGrid(int)));
connect(smoothSlider, SIGNAL(valueChanged(int)),
this, SLOT(setSmoothingFromSlider()));
connect(smoothLineEdit, SIGNAL(returnPressed()),
this, SLOT(setSmoothingFromLineEdit()));
QMenu *fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(tr("&New..."), this,
SLOT(newCyclist()), tr("Ctrl+N"));
fileMenu->addAction(tr("&Open..."), this,
SLOT(openCyclist()), tr("Ctrl+O"));
fileMenu->addAction(tr("&Download ride..."), this,
SLOT(downloadRide()), tr("Ctrl+D"));
QMenuBar *menuBar = new QMenuBar(this);
menuBar->addMenu(fileMenu);
if (last != NULL) {
treeWidget->setCurrentItem(last);
rideSelected();
}
}
void
MainWindow::addRide(QString name)
{
QRegExp rx(rideFileRegExp);
if (!rx.exactMatch(name))
assert(false);
assert(rx.numCaptures() == 6);
QDate date(rx.cap(1).toInt(), rx.cap(2).toInt(),rx.cap(3).toInt());
QTime time(rx.cap(4).toInt(), rx.cap(5).toInt(),rx.cap(6).toInt());
QDateTime dt(date, time);
RideItem *last = new RideItem(allRides, RIDE_TYPE, home.path(), name, dt);
treeWidget->setCurrentItem(last);
}
void
MainWindow::newCyclist()
{
QDir newHome = home;
newHome.cdUp();
QString name = ChooseCyclistDialog::newCyclistDialog(newHome, this);
if (!name.isEmpty()) {
newHome.cd(name);
if (!newHome.exists())
assert(false);
MainWindow *main = new MainWindow(newHome);
main->show();
}
}
void
MainWindow::openCyclist()
{
QDir newHome = home;
newHome.cdUp();
ChooseCyclistDialog d(newHome, false);
d.setModal(true);
if (d.exec() == QDialog::Accepted) {
newHome.cd(d.choice());
if (!newHome.exists())
assert(false);
MainWindow *main = new MainWindow(newHome);
main->show();
}
}
void
MainWindow::downloadRide()
{
(new DownloadRideDialog(this, home))->show();
}
void
MainWindow::rideSelected()
{
assert(treeWidget->selectedItems().size() <= 1);
if (treeWidget->selectedItems().size() == 1) {
QTreeWidgetItem *which = treeWidget->selectedItems().first();
if (which->type() == RIDE_TYPE) {
RideItem *ride = (RideItem*) which;
rideSummary->setHtml(ride->htmlSummary());
rideSummary->setAlignment(Qt::AlignCenter);
allPlot->setData(ride->raw);
return;
}
}
rideSummary->clear();
}
void
MainWindow::resizeEvent(QResizeEvent*)
{
settings.setValue(GC_SETTINGS_MAIN_GEOM, geometry());
}
void
MainWindow::moveEvent(QMoveEvent*)
{
settings.setValue(GC_SETTINGS_MAIN_GEOM, geometry());
}
void
MainWindow::splitterMoved()
{
settings.setValue(GC_SETTINGS_SPLITTER_SIZES, splitter->saveState());
}
void
MainWindow::setSmoothingFromSlider()
{
if (allPlot->smoothing() != smoothSlider->value()) {
allPlot->setSmoothing(smoothSlider->value());
smoothLineEdit->setText(QString("%1").arg(allPlot->smoothing()));
}
}
void
MainWindow::setSmoothingFromLineEdit()
{
int value = smoothLineEdit->text().toInt();
if (value != allPlot->smoothing()) {
allPlot->setSmoothing(value);
smoothSlider->setValue(value);
}
}

66
gui/MainWindow.h Normal file
View File

@@ -0,0 +1,66 @@
/*
* $Id: MainWindow.h,v 1.6 2006/07/11 21:20:21 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef _GC_MainWindow_h
#define _GC_MainWindow_h 1
#include <QDir>
#include <QtGui>
class AllPlot;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(const QDir &home);
void addRide(QString name);
protected:
virtual void resizeEvent(QResizeEvent*);
virtual void moveEvent(QMoveEvent*);
private slots:
void rideSelected();
void splitterMoved();
void newCyclist();
void openCyclist();
void downloadRide();
void setSmoothingFromSlider();
void setSmoothingFromLineEdit();
private:
QDir home;
QSettings settings;
QSplitter *splitter;
QTreeWidget *treeWidget;
QTabWidget *tabWidget;
QTextEdit *rideSummary;
AllPlot *allPlot;
QSlider *smoothSlider;
QLineEdit *smoothLineEdit;
QTreeWidgetItem *allRides;
};
#endif // _GC_MainWindow_h

100
gui/RawFile.cpp Normal file
View File

@@ -0,0 +1,100 @@
/*
* $Id: RawFile.cpp,v 1.2 2006/08/11 19:58:07 srhea Exp $
*
* Copyright (c) 2006 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 "RawFile.h"
#include <QMessageBox>
extern "C" {
#include "../pt.h"
}
struct RawFileReadState
{
QList<RawFilePoint*> &points;
QStringList &errors;
double last_secs, last_miles;
unsigned last_interval;
time_t start_since_epoch;
unsigned rec_int;
RawFileReadState(QList<RawFilePoint*> &points, QStringList &errors) :
points(points), errors(errors), last_secs(0.0), last_miles(0.0),
last_interval(0), start_since_epoch(0), rec_int(0) {}
};
static void
config_cb(unsigned /*interval*/, unsigned rec_int,
unsigned /*wheel_sz_mm*/, void *context)
{
RawFileReadState *state = (RawFileReadState*) context;
// Assume once set, rec_int should never change.
assert((state->rec_int == 0) || (state->rec_int == rec_int));
state->rec_int = rec_int;
}
static void
time_cb(struct tm *, time_t since_epoch, void *context)
{
RawFileReadState *state = (RawFileReadState*) context;
if (state->start_since_epoch == 0)
state->start_since_epoch = since_epoch;
double secs = since_epoch - state->start_since_epoch;
RawFilePoint *point = new RawFilePoint(secs, -1.0, -1.0, -1.0,
state->last_miles, 0, 0,
state->last_interval);
state->points.append(point);
state->last_secs = secs;
}
static void
data_cb(double secs, double nm, double mph, double watts, double miles,
unsigned cad, unsigned hr, unsigned interval, void *context)
{
RawFileReadState *state = (RawFileReadState*) context;
RawFilePoint *point = new RawFilePoint(secs, nm, mph, watts, miles,
cad, hr, interval);
state->points.append(point);
state->last_secs = secs;
state->last_miles = miles;
state->last_interval = interval;
}
static void
error_cb(const char *msg, void *context)
{
RawFileReadState *state = (RawFileReadState*) context;
state->errors.append(QString(msg));
}
RawFile *RawFile::readFile(const QFile &file, QStringList &errors)
{
RawFile *result = new RawFile(file.fileName());
if (!result->file.open(QIODevice::ReadOnly)) {
delete result;
return NULL;
}
FILE *f = fdopen(result->file.handle(), "r");
assert(f);
RawFileReadState state(result->points, errors);
pt_read_raw(f, 0 /* not compat */, &state, config_cb,
time_cb, data_cb, error_cb);
result->rec_int = state.rec_int;
return result;
}

58
gui/RawFile.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* $Id: RawFile.h,v 1.3 2006/08/11 19:58:07 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef _GC_RawFile_h
#define _GC_RawFile_h 1
#include <QDateTime>
#include <QFile>
#include <QList>
#include <QStringList>
struct RawFilePoint
{
double secs, nm, mph, watts, miles;
unsigned cad, hr, interval;
RawFilePoint(double secs, double nm, double mph, double watts,
double miles, unsigned cad, unsigned hr, unsigned interval) :
secs(secs), nm(nm), mph(mph), watts(watts), miles(miles),
cad(cad), hr(hr), interval(interval) {}
};
class RawFile
{
private:
QFile file;
RawFile(QString fileName) {
file.setFileName(fileName);
}
public:
QDateTime startTime;
int rec_int;
QList<RawFilePoint*> points;
static RawFile *readFile(const QFile &file, QStringList &errors);
};
#endif // _GC_RawFile_h

138
gui/RideItem.cpp Normal file
View File

@@ -0,0 +1,138 @@
/*
* $Id: RideItem.cpp,v 1.3 2006/07/09 15:30:34 srhea Exp $
*
* Copyright (c) 2006 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 "RideItem.h"
#include "RawFile.h"
#include "Settings.h"
#include <math.h>
static QString
time_to_string(double secs) {
QString result;
unsigned rounded = (unsigned) round(secs);
bool needs_colon = false;
if (rounded >= 3600) {
result += QString("%1").arg(rounded / 3600);
rounded %= 3600;
needs_colon = true;
}
if (needs_colon || rounded >= 60) {
if (needs_colon)
result += ":";
result += QString("%1").arg(rounded / 60, 2, 10, QLatin1Char('0'));
rounded %= 60;
needs_colon = true;
}
if (needs_colon)
result += ":";
result += QString("%1").arg(rounded, 2, 10, QLatin1Char('0'));
return result;
}
RideItem::RideItem(QTreeWidgetItem *parent, int type, QString path,
QString fileName, const QDateTime &dateTime) :
QTreeWidgetItem(parent, type), path(path),
fileName(fileName), dateTime(dateTime)
{
setText(0, dateTime.toString("ddd"));
setText(1, dateTime.toString("MMM d, yyyy"));
setText(2, dateTime.toString("h:mm AP"));
setTextAlignment(1, Qt::AlignRight);
setTextAlignment(2, Qt::AlignRight);
}
QString
RideItem::htmlSummary()
{
if (summary.isEmpty()) {
QFile file(path + "/" + fileName);
QStringList errors;
raw = RawFile::readFile(file, errors);
summary = ("<center><h2>"
+ dateTime.toString("dddd MMMM d, yyyy, h:mm AP")
+ "</h2></center>");
if (raw == NULL) {
summary += "<p>Error: Can't read file.";
}
else if (errors.empty()) {
double secs_moving = 0.0;
double total_watts = 0.0;
double secs_watts = 0.0;
double secs_hr = 0.0;
double total_hr = 0.0;
double secs_cad = 0.0;
double total_cad = 0.0;
double last_secs = 0.0;
QListIterator<RawFilePoint*> i(raw->points);
while (i.hasNext()) {
RawFilePoint *point = i.next();
double secs_delta = point->secs - last_secs;
if (point->mph > 0.0)
secs_moving += secs_delta;
if (point->watts >= 0.0) {
total_watts += point->watts * secs_delta;
secs_watts += secs_delta;
}
if (point->hr > 0) {
total_hr += point->hr * secs_delta;
secs_hr += secs_delta;
}
if (point->cad > 0) {
total_cad += point->cad * secs_delta;
secs_cad += secs_delta;
}
last_secs = point->secs;
}
summary += "<br><table width=\"80%\" "
"align=\"center\" border=0>";
summary += "<tr><td>Total workout time:</td><td align=\"right\">" +
time_to_string(raw->points.back()->secs);
summary += "<tr><td>Total time riding:</td><td align=\"right\">" +
time_to_string(secs_moving) + "</td></tr>";
summary += QString("<tr><td>Total distance:</td>"
"<td align=\"right\">%1</td></tr>")
.arg(raw->points.back()->miles, 0, 'f', 1);
summary += QString("<tr><td>Average speed:</td>"
"<td align=\"right\">%1</td></tr>")
.arg(raw->points.back()->miles / secs_moving * 3600.0,
0, 'f', 1);
summary += QString("<tr><td>Average power:</td>"
"<td align=\"right\">%1</td></tr>")
.arg((unsigned) round(total_watts / secs_watts));
summary +=QString("<tr><td>Average heart rate:</td>"
"<td align=\"right\">%1</td></tr>")
.arg((unsigned) round(total_hr / secs_hr));
summary += QString("<tr><td>Average cadence:</td>"
"<td align=\"right\">%1</td></tr>")
.arg((unsigned) round(total_cad / secs_cad));
summary += "</table>";
}
else {
summary += "<br>Errors reading file:<ul>";
QStringListIterator i(errors);
while(i.hasNext())
summary += " <li>" + i.next();
summary += "</ul>";
}
}
return summary;
}

42
gui/RideItem.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* $Id: RideItem.h,v 1.2 2006/07/04 12:55:40 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef _GC_RideItem_h
#define _GC_RideItem_h 1
#include <QtGui>
class RawFile;
struct RideItem : public QTreeWidgetItem {
QString path;
QString fileName;
QDateTime dateTime;
QString summary;
RawFile *raw;
RideItem(QTreeWidgetItem *parent, int type, QString path,
QString fileName, const QDateTime &dateTime);
QString htmlSummary();
};
#endif // _GC_RideItem_h

36
gui/Settings.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* $Id: Settings.h,v 1.2 2006/07/04 12:55:40 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef _GC_Settings_h
#define _GC_Settings_h 1
#define GC_SETTINGS_CO "goldencheetah.org"
#define GC_SETTINGS_APP "GoldenCheetah"
#define GC_SETTINGS_LAST "mainwindow/lastOpened"
#define GC_SETTINGS_MAIN_WIDTH "mainwindow/width"
#define GC_SETTINGS_MAIN_HEIGHT "mainwindow/height"
#define GC_SETTINGS_MAIN_X "mainwindow/x"
#define GC_SETTINGS_MAIN_Y "mainwindow/y"
#define GC_SETTINGS_MAIN_GEOM "mainwindow/geometry"
#define GC_SETTINGS_SPLITTER_SIZES "mainwindow/splitterSizes"
#define GC_DATETIME_FORMAT "ddd MMM dd, yyyy, hh:mm AP"
#endif // _GC_Settings_h

10
gui/TODO Normal file
View File

@@ -0,0 +1,10 @@
- Recalc y-max after changing showPower or showHr
- Add cadence, speed, torque to graph
- Remember last settings for showPower, showHr, etc.
- Add interval summary to ride summary
- Switch x-axis from minutes to miles
- Add units to ride summary
- Add weekly summary
- Add cpint

13
gui/addqt.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
# $Id: addqt.sh,v 1.1 2006/08/11 20:01:16 srhea Exp $
QT_PATH=/usr/local/Trolltech/Qt-4.1.1
APP=GoldenCheetah
rm -rf $APP.app/Contents/Frameworks
mkdir -p $APP.app/Contents/Frameworks
cp -R $QT_PATH/lib/QtCore.framework $APP.app/Contents/Frameworks
cp -R $QT_PATH/lib/QtGui.framework $APP.app/Contents/Frameworks
install_name_tool -id @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore $APP.app/Contents/Frameworks/QtCore.framework/Versions/4.0/QtCore
install_name_tool -id @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui $APP.app/Contents/Frameworks/QtGui.framework/Versions/4.0/QtGui
install_name_tool -change $QT_PATH/lib/QtCore.framework/Versions/4.0/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore $APP.app/Contents/MacOs/$APP
install_name_tool -change $QT_PATH/lib/QtGui.framework/Versions/4.0/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui $APP.app/Contents/MacOs/$APP
install_name_tool -change $QT_PATH/lib/QtCore.framework/Versions/4.0/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore $APP.app/Contents/Frameworks/QtGui.framework/Versions/4.0/QtGui

69
gui/main.cpp Normal file
View File

@@ -0,0 +1,69 @@
/*
* $Id: main.cpp,v 1.3 2006/07/04 12:55:40 srhea Exp $
*
* Copyright (c) 2006 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 <QApplication>
#include <QtGui>
#include "ChooseCyclistDialog.h"
#include "MainWindow.h"
#include "Settings.h"
int
main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDir home = QDir::home();
if (!home.exists("Library"))
assert(false);
home.cd("Library");
if (!home.exists("GoldenCheetah"))
if (!home.mkdir("GoldenCheetah"))
assert(false);
home.cd("GoldenCheetah");
QSettings settings(GC_SETTINGS_CO, GC_SETTINGS_APP);
QVariant lastOpened = settings.value(GC_SETTINGS_LAST);
bool anyOpened = false;
if (lastOpened != QVariant()) {
QStringList list = lastOpened.toStringList();
QStringListIterator i(list);
while (i.hasNext()) {
QString cyclist = i.next();
if (home.cd(cyclist)) {
MainWindow *main = new MainWindow(home);
main->show();
home.cdUp();
anyOpened = true;
}
}
}
if (!anyOpened) {
ChooseCyclistDialog d(home, true);
d.setModal(true);
if (d.exec() != QDialog::Accepted)
return 0;
home.cd(d.choice());
if (!home.exists())
assert(false);
MainWindow *main = new MainWindow(home);
main->show();
}
return app.exec();
}

17
gui/mkdmg.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
# $Id: mkdmg.sh,v 1.2 2006/09/06 23:23:03 srhea Exp $
CMD_FILES="COPYING ptdl ptunpk intervals.pl smooth.pl cpint ptpk"
GUI_FILE="GoldenCheetah.app"
VERS=`date +'%Y-%m-%d'`
strip GoldenCheetah.app/Contents/MacOS/GoldenCheetah
rm -rf tmp.dmg
SIZE=`cd ..; du -csk $CMD_FILES gui/GoldenCheetah.app | grep total | awk '{printf "%.0fm", $1/1024+5}'`
echo "SIZE=$SIZE, VERS=$VERS"
hdiutil create -size $SIZE -fs HFS+ -volname "Golden Cheetah $VERS" tmp.dmg
hdiutil attach tmp.dmg
cp -R GoldenCheetah.app /Volumes/Golden\ Cheetah\ $VERS/
cd .. && cp $CMD_FILES /Volumes/Golden\ Cheetah\ $VERS/ && cd -
hdiutil detach /Volumes/Golden\ Cheetah\ $VERS/
hdiutil convert tmp.dmg -format UDZO -o GoldenCheetah_$VERS.dmg
hdiutil internet-enable -yes GoldenCheetah_$VERS.dmg
rm -rf tmp.dmg

113
intervals.pl Executable file
View File

@@ -0,0 +1,113 @@
#!/usr/bin/perl -w
#
# $Id: intervals.pl,v 1.2 2006/08/11 19:53:50 srhea Exp $
#
# Copyright (c) 2006 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
#
use strict;
my $interval = -1;
my $time_start = 0;
my $time_end = 0;
my $mile_start = 0;
my $mile_end = 0;
my $watts_max = 0;
my $watts_sum = 0;
my $watts_cnt = 0;
my $hrate_max = 0;
my $hrate_sum = 0;
my $hrate_cnt = 0;
my $caden_max = 0;
my $caden_sum = 0;
my $caden_cnt = 0;
my $speed_max = 0;
my $speed_sum = 0;
my $speed_cnt = 0;
sub sumarize {
my $dur = $time_end - $time_start;
my $len = $mile_end - $mile_start;
my $minutes = int($dur);
my $seconds = int(60 * ($dur - int($dur)));
my $watts_avg = int($watts_sum / $watts_cnt);
my $hrate_avg = ($hrate_cnt == 0) ? 0 : int($hrate_sum / $hrate_cnt);
my $caden_avg = ($caden_cnt == 0) ? 0 : int($caden_sum / $caden_cnt);
my $speed_avg = int($speed_sum / $speed_cnt);
printf "%2d\t%2d:%02d\t%5.1f\t%4d\t%4d\t%3d\t%3d\t%3d\t%3d\t%0.1f\t%0.1f\n", $interval, $minutes, $seconds, $len, $watts_avg, $watts_max, $hrate_avg, $hrate_max, $caden_avg, $caden_max, $speed_avg, $speed_max;
$watts_sum = 0;
$watts_cnt = 0;
$watts_max = 0;
$hrate_max = 0;
$hrate_sum = 0;
$hrate_cnt = 0;
$caden_max = 0;
$caden_sum = 0;
$caden_cnt = 0;
$speed_max = 0;
$speed_sum = 0;
$speed_cnt = 0;
}
print "\t\t\tPower\t\tHeart Rate\tCadence\t\tSpeed\n";
print "Int\t Dur\tDist\t Avg\t Max\tAvg\tMax\tAvg\tMax\tAvg\tMax\n";
while (<>) {
if (m/^#/) {
}
else {
my @cols = split;
if ($#cols != 7) {
print STDERR "Wrong number of columns: $_";
exit 1;
}
my ($min, $torq, $speed, $watts, $miles, $caden, $hrate, $id) = @cols;
$mile_end = $miles;
$time_end = $min;
if ($watts != "NaN") {
$watts_sum += $watts;
$watts_cnt += 1;
if ($watts > $watts_max) { $watts_max = $watts; }
}
if ($hrate != "NaN") {
$hrate_sum += $hrate;
$hrate_cnt += 1;
if ($hrate > $hrate_max) { $hrate_max = $hrate; }
}
if ($caden != "NaN") {
$caden_sum += $caden;
$caden_cnt += 1;
if ($caden > $caden_max) { $caden_max = $caden; }
}
if ($speed != "NaN") {
$speed_sum += $speed;
$speed_cnt += 1;
if ($speed > $speed_max) { $speed_max = $speed; }
}
if ($id != $interval) {
if ($interval != -1) {
sumarize();
}
$interval = $id;
$time_start = $min;
$mile_start = $miles;
}
}
}
sumarize();

75
notes.txt Normal file
View File

@@ -0,0 +1,75 @@
Version header:
sometimes bytes added before 0x56
00 56 45 52 20 30 .VER 0
32 2e 32 31 20 50 2.21 P
52 4f 00 00 00 00 RO....
00 00 00 00 00 00 ......
00 00 00 00 0d 0a ....\r\n
Data:
Get 12 packets of 1542 (0x606) bytes each.
Where is that encoded?
Each row is 6 bytes.
Rows that start with 0x8? are the data for one period.
Column Meaning Format
1 ???
2, n1 torque 4 most sig bits, N m
2, n2 km/h 4 most sig bits, 0x24000 / value * 0.1
3 torque 8 least sig bits, N m
4 km/h 8 least sig bits, 0x24000 / value * 0.1
5 Cadence 1-byte integer
6 Heartrate 1-byte integer
First row is always:
57 56 55 64 02 15
Base period in thousands of a minute = 0x15...
A row starting with 0x60 encodes the time of day that measurements started.
This is always the second record in the stream. Also, when the computer goes
to sleep and then wakes up again, it writes one of these records first.
Example: 60 06 04 9c 2a 30
byte 2 year
byte 3 month
byte 4, bottom 5 bits day
byte 5, bottom 5 bits hour
byte 6, bottom 6 bits minute
top 3 bits of byte 4 followed by top 3 bits of byte 5 is pretty close to the
right seconds value, but not perfect...
A row starting with 0x40 encodes:
bytes 2-3 the wheel circumference in mm
byte 4 the interval number
byte 5 the recording interval, where
byte value interval
0x0 1
0x1 2
0x3 5
0x7 10
0x17 30
Example:
40 08 30 02 07 00
wheel size in mm = 0x0830
interval 2
recording interval 10

8
package.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
VERSION=`date +'%Y-%m-%d'`
FILES="COPYING Makefile pt.h pt.c ptdl.c ptunpk.c intervals.pl smooth.pl"
FILES="$FILES cpint.c cpint.h cpint-cmd.c ptpk.c"
mkdir gc_$VERSION
cp $FILES gc_$VERSION
tar cvzf gc_$VERSION.tgz gc_$VERSION
rm -rf gc_$VERSION

632
pt.c Normal file
View File

@@ -0,0 +1,632 @@
/*
* $Id: pt.c,v 1.9 2006/09/06 23:23:03 srhea Exp $
*
* Copyright (c) 2006 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 <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pt.h"
#define MAGIC_CONSTANT 147375.0
#define PI 3.14159265
#define TIME_UNIT_MIN 0.021
#define LBFIN_TO_NM 0.11298483
#define KM_TO_MI 0.62137119
#define BAD_LBFIN_TO_NM_1 0.112984
#define BAD_LBFIN_TO_NM_2 0.1129824
#define BAD_KM_TO_MI 0.62
unsigned pt_debug_level;
static unsigned char
check(unsigned value)
{
assert(value < 256);
return (unsigned char) value;
}
int
pt_find_device(char *result[], int capacity)
{
regex_t reg;
DIR *dirp;
struct dirent *dp;
int count = 0;
if (regcomp(&reg, "^cu\\.(usbserial-[0-9A-F]+|KeySerial[0-9])$",
REG_EXTENDED|REG_NOSUB)) {
assert(0);
}
dirp = opendir("/dev");
while ((count < capacity) && ((dp = readdir(dirp)) != NULL)) {
if (regexec(&reg, dp->d_name, 0, NULL, 0) == 0) {
result[count] = malloc(6 + strlen(dp->d_name));
sprintf(result[count], "/dev/%s", dp->d_name);
++count;
}
}
return count;
}
#define KSDEVSTR "/dev/cu.KeySerial"
int
pt_hwecho(const char *device)
{
return strncmp(device, KSDEVSTR, strlen(KSDEVSTR)) == 0;
}
void
pt_make_async(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl");
assert(0);
}
}
int
pt_read_version(struct pt_read_version_state *state, int fd, int hwecho)
{
char c = 0x56;
int n;
if (state->state == 0) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Writing 0x%x to device.\n", (unsigned) c);
if ((n = write(fd, &c, 1)) < 1) {
perror("write");
exit(1);
}
state->state = 1;
state->i = 0;
}
if (state->state == 1) {
if (hwecho) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Calling read on device.\n");
n = read(fd, &c, 1);
if (n <= 0) {
if ((n < 0) && (errno == EAGAIN)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Need read.\n");
return PT_NEED_READ;
}
perror("read");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Read %d bytes.\n", n);
assert(n == 1);
}
state->state = 2;
}
assert(state->state == 2);
while (state->i < 29) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Need %d bytes. Calling read on device.\n",
29 - state->i);
n = read(fd, state->buf + state->i, sizeof(state->buf) - state->i);
if (n <= 0) {
if ((n < 0) && (errno == EAGAIN)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Need read.\n");
return PT_NEED_READ;
}
perror("read");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Read %d bytes.\n", n);
state->i += n;
}
return PT_DONE;
}
int
pt_read_data(struct pt_read_data_state *state,
int fd, int hwecho,
void (*time_cb)(struct tm *, void *),
void (*record_cb)(unsigned char *, void *),
void *user_data)
{
char c = 0x44;
int j, n;
unsigned csum;
struct tm time;
if (state->state == 0) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Writing 0x%x to device.\n", (unsigned) c);
if ((n = write(fd, &c, 1)) < 1) {
perror("write");
exit(1);
}
state->block = 1;
state->i = 0;
state->state = 1;
}
if (state->state == 1) {
if (hwecho) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Calling read on device.\n");
n = read(fd, &c, 1);
if (n <= 0) {
if ((n < 0) && (errno == EAGAIN)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Need read.\n");
return PT_NEED_READ;
}
perror("read");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Read %d bytes.\n", n);
assert(n == 1);
}
state->state = 2;
}
if (state->state == 2) {
while (state->i < (int) sizeof(state->header)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Calling read on device.\n");
n = read(fd, state->header + state->i,
sizeof(state->header) - state->i);
if (n <= 0) {
if ((n < 0) && (errno == EAGAIN)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Need read.\n");
return PT_NEED_READ;
}
perror("read");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Read %d bytes.\n", n);
state->i += n;
}
state->state = 3;
state->i = 0;
}
while (1) {
if (state->state == 3) {
while (state->i < (int) sizeof(state->buf)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Calling read on device.\n");
n = read(fd, state->buf + state->i,
sizeof(state->buf) - state->i);
if (n <= 0) {
if ((n < 0) && (errno == EAGAIN)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Need read.\n");
return PT_NEED_READ;
}
perror("read");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Read %d bytes.\n", n);
state->i += n;
/* TODO: why is this next if statement here? */
if ((state->i == 2) && (state->buf[0] == 0x0d)
&& (state->buf[1] == 0x0a)) {
return PT_DONE;
}
}
if (state->block == 1) {
n = 0;
if (pt_is_config(state->buf + n))
n += 6;
if (!pt_is_time(state->buf + n)
|| (pt_unpack_time(state->buf + n, &time) == -1))
time_cb(NULL, user_data);
else
time_cb(&time, user_data);
record_cb(state->header, user_data);
}
csum = 0;
for (j = 0; j < state->i - 1; ++j)
csum += state->buf[j];
if ((csum % 256) != state->buf[state->i-1]) {
fprintf(stderr, "\nbad checksum on block %d: %d vs %d",
state->block, state->buf[state->i-1], csum);
}
for (j = 0; j < state->i - 1; j += 6) {
if (state->buf[j])
record_cb(state->buf + j, user_data);
else
break;
}
c = 0x71;
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Writing 0x%x to device.\n", (unsigned) c);
n = write(fd, &c, 1);
if (n < 1) {
perror("write");
exit(1);
}
++(state->block);
state->i = 0;
state->state = 4;
}
assert(state->state == 4);
if (hwecho) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Calling read on device.\n");
n = read(fd, &c, 1);
if (n <= 0) {
if ((n < 0) && (errno == EAGAIN)) {
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Need read.\n");
return PT_NEED_READ;
}
perror("read");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Read %d bytes.\n", n);
assert(n == 1);
}
state->state = 3;
}
return PT_DONE;
}
int
pt_is_time(unsigned char *buf)
{
return buf[0] == 0x60;
}
time_t
pt_unpack_time(unsigned char *buf, struct tm *time)
{
memset(time, 0, sizeof(*time));
time->tm_year = 2000 + buf[1] - 1900;
time->tm_mon = buf[2] - 1;
time->tm_mday = buf[3] & 0x1f;
time->tm_hour = buf[4] & 0x1f;
time->tm_min = buf[5] & 0x3f;
time->tm_sec = ((buf[3] >> 5) << 3) | (buf[4] >> 5);
time->tm_isdst = -1;
return mktime(time);
}
int
pt_is_config(unsigned char *buf)
{
return buf[0] == 0x40;
}
int
pt_unpack_config(unsigned char *buf, unsigned *interval,
unsigned *last_interval, unsigned *rec_int,
unsigned *wheel_sz_mm)
{
*wheel_sz_mm = (buf[1] << 8) | buf[2];
/* Data from device wraps interval after 9... */
if (buf[3] != *last_interval) {
*last_interval = buf[3];
++*interval;
}
switch (buf[4]) {
case 0x0: *rec_int = 1; break;
case 0x1: *rec_int = 2; break;
case 0x3: *rec_int = 5; break;
case 0x7: *rec_int = 10; break;
case 0x17: *rec_int = 30; break;
default:
return -1;
}
return 0;
}
int
pt_is_data(unsigned char *buf)
{
return (buf[0] & 0x80) == 0x80;
}
static double
my_round(double x)
{
int i = (int) x;
double z = x - i;
/* For some unknown reason, the PowerTap software rounds 196.5 down... */
if ((z > 0.5) || ((z == 0.5) && (i != 196)))
++i;
return i;
}
void
pt_unpack_data(unsigned char *buf, int compat, unsigned rec_int,
unsigned wheel_sz_mm, double *time_secs, double *torque_Nm,
double *mph, double *watts, double *dist_m, unsigned *cad,
unsigned *hr)
{
double kph10;
unsigned speed;
unsigned torque_inlbs;
double rotations;
double radians;
double joules;
*time_secs += rec_int * TIME_UNIT_MIN * 60.0;
torque_inlbs = ((buf[1] & 0xf0) << 4) | buf[2];
if (torque_inlbs == 0xfff)
torque_inlbs = 0;
speed = ((buf[1] & 0x0f) << 8) | buf[3];
if ((speed == 0) || (speed == 0xfff)) {
*mph = -1.0;
*watts = -1.0;
}
else {
if (compat)
*torque_Nm = torque_inlbs * BAD_LBFIN_TO_NM_2;
else
*torque_Nm = torque_inlbs * LBFIN_TO_NM;
kph10 = MAGIC_CONSTANT / speed;
if (compat)
*mph = my_round(kph10) / 10.0 * BAD_KM_TO_MI;
else
*mph = kph10 / 10.0 * KM_TO_MI;
rotations = rec_int * TIME_UNIT_MIN * 100000.0 * kph10
/ wheel_sz_mm / 60.0;
radians = rotations * 2.0 * PI;
joules = *torque_Nm * radians;
*watts = joules / (rec_int * TIME_UNIT_MIN * 60);
if (compat)
*watts = my_round(*watts);
else
*watts = round(*watts);
}
if (compat)
*torque_Nm = torque_inlbs * BAD_LBFIN_TO_NM_1;
*dist_m += (buf[0] & 0x7f) * wheel_sz_mm / 1000.0;
*cad = buf[4];
if (*cad == 0xff)
*cad = 0;
*hr = buf[5];
if (*hr == 0xff)
*hr = 0;
}
void
pt_write_data(FILE *out, unsigned char *buf)
{
int i;
for (i = 0; i < 5; ++i)
fprintf(out, "%02x ", buf[i]);
fprintf(out, "%02x\n", buf[i]);
}
void
pt_pack_header(unsigned char *buf)
{
unsigned char src[] = { 0x57, 0x56, 0x55, 0x64, 0x02, 0x15 };
memcpy(buf, src, 6);
}
void
pt_pack_time(unsigned char *buf, struct tm *time)
{
buf[0] = 0x60;
buf[1] = check(time->tm_year + 1900 - 2000);
buf[2] = check(time->tm_mon + 1);
buf[3] = check(time->tm_mday) | check((time->tm_sec >> 3) << 5);
buf[4] = check(time->tm_hour) | check((time->tm_sec & 0x7) << 5);
buf[5] = check(time->tm_min);
}
void
pt_pack_config(unsigned char *buf, unsigned interval,
unsigned rec_int, unsigned wheel_sz_mm)
{
buf[0] = 0x40;
buf[1] = check(wheel_sz_mm >> 8);
buf[2] = wheel_sz_mm & 0xff;
buf[3] = check(interval % 9);
switch (rec_int) {
case 1: buf[4] = 0x0; break;
case 2: buf[4] = 0x1; break;
case 5: buf[4] = 0x3; break;
case 10: buf[4] = 0x7; break;
case 30: buf[4] = 0x17; break;
default: assert(0);
}
buf[5] = 0x0;
}
void
pt_pack_data(unsigned char *buf, unsigned wheel_sz_mm, double nm,
double mph, double miles, unsigned cad, unsigned hr)
{
double rotations = miles / BAD_KM_TO_MI * 1000.00 * 1000.0 / wheel_sz_mm;
unsigned inlbs = round(nm / BAD_LBFIN_TO_NM_2);
double kph10 = mph * 10.0 / BAD_KM_TO_MI;
unsigned speed;
if (mph == -1.0)
speed = 0xfff;
else
speed = round(MAGIC_CONSTANT / kph10);
buf[0] = 0x80 | check(round(rotations));
buf[1] = ((inlbs & 0xf00) >> 4) | ((speed & 0xf00) >> 8);
buf[2] = inlbs & 0xff;
buf[3] = speed & 0xff;
buf[4] = check(cad);
buf[5] = check(hr);
}
void
pt_read_raw(FILE *in, int compat, void *context,
void (*config_cb)(unsigned interval, unsigned rec_int,
unsigned wheel_sz_mm, void *context),
void (*time_cb)(struct tm *time, time_t since_epoch, void *context),
void (*data_cb)(double secs, double nm, double mph,
double watts, double miles, unsigned cad,
unsigned hr, unsigned interval, void *context),
void (*error_cb)(const char *msg, void *context))
{
unsigned interval = 0;
unsigned last_interval = 0;
unsigned wheel_sz_mm = 0;
unsigned rec_int = 0;
int i, n, row = 0;
unsigned char buf[6];
unsigned sbuf[6];
double meters = 0.0;
double secs = 0.0, start_secs = 0.0;
double miles;
double mph;
double nm;
double watts;
unsigned cad;
unsigned hr;
struct tm time;
time_t since_epoch;
char ebuf[256];
while ((n = fscanf(in, "%x %x %x %x %x %x\n",
sbuf, sbuf+1, sbuf+2, sbuf+3, sbuf+4, sbuf+5)) == 6) {
++row;
for (i = 0; i < 6; ++i) {
if (sbuf[i] > 0xff) { n = 1; break; }
buf[i] = sbuf[i];
}
if (row == 1) {
/* Serial number? */
}
else if (pt_is_config(buf)) {
if (pt_unpack_config(buf, &interval, &last_interval,
&rec_int, &wheel_sz_mm) < 0) {
sprintf(ebuf, "Couldn't unpack config record.");
if (error_cb) error_cb(ebuf, context);
return;
}
if (config_cb) config_cb(interval, rec_int, wheel_sz_mm, context);
}
else if (pt_is_time(buf)) {
since_epoch = pt_unpack_time(buf, &time);
if (start_secs == 0.0)
start_secs = since_epoch;
else
secs = since_epoch - start_secs;
if (time_cb) time_cb(&time, since_epoch, context);
}
else if (pt_is_data(buf)) {
if (wheel_sz_mm == 0) {
sprintf(ebuf, "Read data row before wheel size set.");
if (error_cb) error_cb(ebuf, context);
return;
}
pt_unpack_data(buf, compat, rec_int, wheel_sz_mm, &secs,
&nm, &mph, &watts, &meters, &cad, &hr);
if (compat)
miles = round(meters) / 1000.0 * BAD_KM_TO_MI;
else
miles = meters / 1000.0 * KM_TO_MI;
if (data_cb)
data_cb(secs, nm, mph, watts, miles, cad,
hr, interval, context);
}
else {
sprintf(ebuf, "Unknown record type 0x%x on row %d.", buf[0], row);
if (error_cb) error_cb(ebuf, context);
return;
}
}
if (n != -1) {
sprintf(ebuf, "Parse error on row %d.", row);
if (error_cb) error_cb(ebuf, context);
return;
}
}
#define NMATCH 9
void
pt_read_dat(FILE *in, void (*record_cb)(double, double, double, int,
double, int, int, int, void*),
void *user_data)
{
regex_t reg_com, reg_dat;
regmatch_t pmatch[NMATCH];
char line[256];
double min, nm, mph, miles;
int watts, cad, hr, intv;
int i, len;
if (regcomp(&reg_com, "^#", REG_EXTENDED | REG_NOSUB))
assert(0);
if (regcomp(&reg_dat, "^([0-9]+\\.[0-9]+) +([0-9]+\\.[0-9]+) +"
"([0-9]+\\.[0-9]+|NaN) +([0-9]+|NaN) +([0-9]+\\.[0-9]+) +"
"([0-9]+) +([0-9]+|NaN) +([0-9]+)$", REG_EXTENDED))
assert(0);
while (fgets(line, sizeof(line), in)) {
len = strlen(line);
if (!line[len-1] == '\n')
assert(0);
line[len-1] = '\0';
if (regexec(&reg_com, line, 0, NULL, 0) == 0) {
/* do nothing */
}
else if (regexec(&reg_dat, line, NMATCH, pmatch, 0) == 0) {
for (i = 0; i < NMATCH; ++i)
line[pmatch[i].rm_eo] = '\0';
if (sscanf(line + pmatch[1].rm_so, "%lf", &min) != 1)
assert(0);
if (sscanf(line + pmatch[2].rm_so, "%lf", &nm) != 1)
assert(0);
if (sscanf(line + pmatch[3].rm_so, "%lf", &mph) != 1)
mph = -1.0;
if (sscanf(line + pmatch[4].rm_so, "%d", &watts) != 1)
watts = -1;
if (sscanf(line + pmatch[5].rm_so, "%lf", &miles) != 1)
assert(0);
if (sscanf(line + pmatch[6].rm_so, "%d", &cad) != 1)
assert(0);
if (sscanf(line + pmatch[7].rm_so, "%d", &hr) != 1)
hr = -1;
if (sscanf(line + pmatch[8].rm_so, "%d", &intv) != 1)
assert(0);
record_cb(min, nm, mph, watts, miles, cad, hr, intv, user_data);
}
else {
fprintf(stderr, "Bad line: \"%s\"\n", line);
exit(1);
}
}
}

98
pt.h Normal file
View File

@@ -0,0 +1,98 @@
/*
* $Id: pt.h,v 1.9 2006/09/06 23:23:03 srhea Exp $
*
* Copyright (c) 2006 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
*/
#ifndef __pt_h
#define __pt_h 1
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#define PT_DEBUG_NONE 0
#define PT_DEBUG_MAX 1
extern unsigned pt_debug_level;
extern int pt_find_device(char *result[], int capacity);
extern int pt_hwecho(const char *device);
#define PT_DONE 0
#define PT_NEED_READ 1
extern void pt_make_async(int fd);
struct pt_read_version_state {
int state;
int i;
unsigned char buf[30];
};
extern int pt_read_version(struct pt_read_version_state *state,
int fd, int hwecho);
struct pt_read_data_state {
int state;
int i;
unsigned block;
unsigned char header[6];
unsigned char buf[256 * 6 + 1];
};
extern int pt_read_data(struct pt_read_data_state *state,
int fd, int hwecho,
void (*time_cb)(struct tm *, void *),
void (*record_cb)(unsigned char *, void *),
void *user_data);
extern int pt_is_time(unsigned char *buf);
extern time_t pt_unpack_time(unsigned char *buf, struct tm *time);
extern int pt_is_config(unsigned char *buf);
extern int pt_unpack_config(unsigned char *buf, unsigned *interval,
unsigned *last_interval, unsigned *rec_int,
unsigned *wheel_sz_mm);
extern int pt_is_data(unsigned char *buf);
extern void pt_unpack_data(unsigned char *buf, int compat, unsigned rec_int,
unsigned wheel_sz_mm, double *time_secs,
double *torque_Nm, double *mph, double *watts,
double *dist_m, unsigned *cad, unsigned *hr);
void pt_write_data(FILE *out, unsigned char *buf);
void pt_pack_header(unsigned char *buf);
void pt_pack_time(unsigned char *buf, struct tm *time);
void pt_pack_config(unsigned char *buf, unsigned interval,
unsigned rec_int, unsigned wheel_sz_mm);
void pt_pack_data(unsigned char *buf, unsigned wheel_sz_mm, double nm,
double mph, double miles, unsigned cad, unsigned hr);
extern void pt_read_raw(FILE *in, int compat, void *context,
void (*config_cb)(unsigned interval, unsigned rec_int,
unsigned wheel_sz_mm, void *context),
void (*time_cb)(struct tm *time, time_t since_epoch, void *context),
void (*data_cb)(double secs, double nm, double mph,
double watts, double miles, unsigned cad,
unsigned hr, unsigned interval, void *context),
void (*error_cb)(const char *msg, void *context));
extern void pt_read_dat(FILE *in,
void (*record_cb)(double, double, double, int,
double, int, int, int, void*),
void *user_data);
#endif /* __pt_h */

231
ptdl.c Normal file
View File

@@ -0,0 +1,231 @@
/*
* $Id: ptdl.c,v 1.10 2006/09/06 23:23:03 srhea Exp $
*
* Copyright (c) 2006 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 <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pt.h"
#define MAX_DEVICES 20
int force;
static void
time_cb(struct tm *time, void *user_data)
{
char outname[24];
FILE **out = (FILE**) user_data;
if (!*out) {
if (!time) {
fprintf(stderr, "Can't find ride time;"
" specify output file with -o.\n");
exit(1);
}
sprintf(outname, "%04d_%02d_%02d_%02d_%02d_%02d.raw",
time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec);
assert(strlen(outname) == sizeof(outname) - 1);
fprintf(stderr, "done.\nWriting to %s.\n", outname);
if ((*out = fopen(outname, "r")) != NULL) {
if (force)
fclose(*out);
else {
fprintf(stderr, "Error: %s already exists! "
"Specify -f to overwrite it.\n", outname);
exit(1);
}
}
if ((*out = fopen(outname, "w")) == NULL) {
fprintf(stderr, "Couldn't open %s for writing: %s",
outname, strerror(errno));
exit(1);
}
fprintf(stderr, "Reading ride data...");
fflush(stderr);
}
}
static void
record_cb(unsigned char *buf, void *user_data)
{
static int count = 0;
int i;
FILE **out = (FILE**) user_data;
for (i = 0; i < 6; ++i)
fprintf(*out, "%02x%s", buf[i], (i == 5) ? "\n" : " ");
if ((++count % 256) == 0) {
fprintf(stderr, ".");
fflush(stderr);
}
}
static void
usage(const char *progname)
{
fprintf(stderr, "usage: %s [-d <device>] [-e] [-f] [-o <output file>]\n",
progname);
exit(1);
}
int
main(int argc, char *argv[])
{
int i, ch, fd, r;
char *devices[MAX_DEVICES];
int dev_cnt = 0;
char *outname;
FILE *out = NULL;
struct pt_read_version_state vstate;
struct pt_read_data_state dstate;
struct timeval timeout;
fd_set readfds;
int hwecho = 0;
while ((ch = getopt(argc, argv, "d:efho:v")) != -1) {
switch (ch) {
case 'd':
devices[0] = optarg;
dev_cnt = 1;
break;
case 'e':
hwecho = 1;
break;
case 'f':
force = 1;
break;
case 'o':
outname = optarg;
if (strcmp(outname, "-") == 0)
out = stdout;
else if ((out = fopen(outname, "w")) == NULL) {
fprintf(stderr, "Couldn't open %s for writing: %s",
outname, strerror(errno));
exit(1);
}
break;
case 'v':
pt_debug_level = PT_DEBUG_MAX;
break;
case 'h':
case '?':
default:
usage(argv[0]);
}
}
argc -= optind;
argv += optind;
if (!dev_cnt) {
dev_cnt = pt_find_device(devices, MAX_DEVICES);
if (dev_cnt == 0) {
fprintf(stderr, "Can't find device; specify one with -d.\n");
exit(1);
}
if (dev_cnt > 1) {
fprintf(stderr, "Multiple devices present; specify one with -d:\n");
for (i = 0; i < dev_cnt; ++i)
fprintf(stderr, " %s\n", devices[i]);
exit(1);
}
}
fprintf(stderr, "Reading from %s.\n", devices[0]);
if (pt_hwecho(devices[0]))
hwecho = 1;
if (hwecho)
fprintf(stderr, "Expecting hardware echo.\n");
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Opening device %s.\n", devices[0]);
fd = open(devices[0], O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
perror("open");
exit(1);
}
fprintf(stderr, "Reading version information...");
fflush(stderr);
pt_make_async(fd);
memset(&vstate, 0, sizeof(vstate));
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "\nCalling pt_read_version.\n");
while ((r = pt_read_version(&vstate, fd, hwecho)) != PT_DONE) {
assert(r == PT_NEED_READ);
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Calling select.\n");
select(fd + 1, &readfds, NULL, NULL, &timeout);
if (!FD_ISSET(fd, &readfds)) {
fprintf(stderr, "timeout.\n");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "\nCalling pt_read_version.\n");
}
fprintf(stderr, "done.\n");
close(fd);
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Opening device %s.\n", devices[0]);
fd = open(devices[0], O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
perror("open");
exit(1);
}
if (out)
fprintf(stderr, "Writing to %s.\nReading ride data...", outname);
else
fprintf(stderr, "Reading ride time...");
fflush(stderr);
pt_make_async(fd);
memset(&dstate, 0, sizeof(dstate));
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "\nCalling pt_read_data.\n");
while ((r = pt_read_data(&dstate, fd, hwecho, time_cb,
record_cb, &out)) != PT_DONE) {
assert(r == PT_NEED_READ);
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "Calling select.\n");
select(fd + 1, &readfds, NULL, NULL, &timeout);
if (!FD_ISSET(fd, &readfds)) {
fprintf(stderr, "timeout.\n");
exit(1);
}
if (pt_debug_level >= PT_DEBUG_MAX)
fprintf(stderr, "\nCalling pt_read_data.\n");
}
fprintf(stderr, "done.\n");
return 0;
}

229
ptpk.c Normal file
View File

@@ -0,0 +1,229 @@
/*
* $Id: ptpk.c,v 1.1 2006/05/27 16:17:25 srhea Exp $
*
* Copyright (c) 2006 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 <assert.h>
#include <errno.h>
#include <math.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "pt.h"
void
pt_pack(FILE *in, FILE *out, int wheel_sz_mm, int rec_int)
{
double mins, nm, mph, watts, miles;
int cad, hr, interval = 0;
double last_mins = 0.0, last_miles = 0.0;
int last_interval = 0;
int i;
struct tm start_tm, inc_tm;
time_t start_time, inc_time;
int lineno = 1;
char line[256];
unsigned char buf[6];
regex_t date_reg;
int date_nmatch = 7;
regmatch_t *date_pmatch =
(regmatch_t*) calloc(date_nmatch, sizeof(regmatch_t));
regex_t rider_reg;
int rider_nmatch = 12;
regmatch_t *rider_pmatch =
(regmatch_t*) calloc(rider_nmatch, sizeof(regmatch_t));
if (regcomp(&date_reg, "^<RideDate>([0-9][0-9]?)\\\\([0-9][0-9]?)\\\\"
"([0-9][0-9][0-9][0-9]) +([0-9][0-9]?):([0-9][0-9]?):"
"([0-9][0-9]?)</RideDate> *$", REG_EXTENDED))
assert(0);
if (regcomp(&rider_reg, "^<Rider><Minutes>([0-9]+\\.[0-9]+)</Minutes>"
"<TorqinLBS>([0-9]+(\\.[0-9]+)?)</TorqinLBS>"
"<Mph>([0-9]+(\\.[0-9]+)?|NaN)</Mph><Watts>([0-9]+|NaN)</Watts>"
"<Miles>([0-9]+(\\.[0-9]+)?)</Miles><Cadence>([0-9]+)</Cadence>"
"<Hrate>([0-9]+|NaN)</Hrate><ID>([0-9]+)</ID></Rider>$",
REG_EXTENDED))
assert(0);
pt_pack_header(buf);
pt_write_data(out, buf);
while (fgets(line, sizeof(line), in)) {
line[strlen(line) - 1] = '\0'; /* drop newline */
if (strcmp(line, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>") == 0) {
/* ignore it */
}
else if (strcmp(line, "<RideData>") == 0) {
/* ignore it */
}
else if (strcmp(line, "</RideData>") == 0) {
/* ignore it */
}
else if (!regexec(&date_reg, line, date_nmatch, date_pmatch, 0)) {
for (i = 1; i < date_nmatch; ++i)
line[date_pmatch[i].rm_eo] = '\0';
memset(&start_tm, 0, sizeof(start_tm));
start_tm.tm_year = atoi(line + date_pmatch[3].rm_so) - 1900;
start_tm.tm_mon = atoi(line + date_pmatch[1].rm_so) - 1;
start_tm.tm_mday = atoi(line + date_pmatch[2].rm_so);
start_tm.tm_hour = atoi(line + date_pmatch[4].rm_so);
start_tm.tm_min = atoi(line + date_pmatch[5].rm_so);
start_tm.tm_sec = atoi(line + date_pmatch[6].rm_so);
start_tm.tm_isdst = -1;
start_time = mktime(&start_tm);
assert(start_time != -1);
pt_pack_time(buf, &start_tm);
pt_write_data(out, buf);
pt_pack_config(buf, interval, rec_int, wheel_sz_mm);
pt_write_data(out, buf);
}
else if (!regexec(&rider_reg, line, rider_nmatch, rider_pmatch, 0)) {
for (i = 1; i < rider_nmatch; ++i)
line[rider_pmatch[i].rm_eo] = '\0';
mins = atof(line + rider_pmatch[1].rm_so);
nm = atof(line + rider_pmatch[2].rm_so);
if (strcmp(line + rider_pmatch[4].rm_so, "NaN") == 0)
mph = -1.0;
else
mph = atof(line + rider_pmatch[4].rm_so);
if (strcmp(line + rider_pmatch[6].rm_so, "NaN") == 0)
watts = -1.0;
else
watts = atof(line + rider_pmatch[6].rm_so);
miles = atof(line + rider_pmatch[7].rm_so);
cad = atoi(line + rider_pmatch[9].rm_so);
if (strcmp(line + rider_pmatch[10].rm_so, "NaN") == 0)
hr = 255;
else
hr = atoi(line + rider_pmatch[10].rm_so);
interval = atoi(line + rider_pmatch[11].rm_so);
if (mins - last_mins - 0.021 > 1.0 / 60.0) {
inc_time = start_time + round((mins - 0.021000001) * 60.0);
if (!localtime_r(&inc_time, &inc_tm))
assert(0);
pt_pack_time(buf, &inc_tm);
pt_write_data(out, buf);
pt_pack_config(buf, interval, rec_int, wheel_sz_mm);
pt_write_data(out, buf);
}
else if (last_interval != interval) {
pt_pack_config(buf, interval, rec_int, wheel_sz_mm);
pt_write_data(out, buf);
}
pt_pack_data(buf, wheel_sz_mm, nm, mph,
miles - last_miles, cad, hr);
pt_write_data(out, buf);
last_mins = mins;
last_interval = interval;
last_miles = miles;
}
else {
fprintf(stderr, "ERROR: line %d unrecognized: \"%s\"\n",
lineno, line);
exit(1);
}
++lineno;
}
}
static void
usage(const char *progname)
{
fprintf(stderr, "usage: %s [-o <output file>] [-r <recording interval>]"
"[-w <wheel size (mm)>] [<input file>]\n",
progname);
exit(1);
}
int
main(int argc, char *argv[])
{
int ch, i;
int wheel_sz_mm = 2096;
int rec_int = 1;
char *inname = NULL, *outname = NULL;
FILE *in, *out = NULL;
while ((ch = getopt(argc, argv, "ho:r:w:")) != -1) {
switch (ch) {
case 'o':
outname = optarg;
if (strcmp(outname, "-") == 0) {
out = stdout;
outname = "STDOUT";
}
break;
case 'r':
rec_int = atoi(optarg);
break;
case 'w':
wheel_sz_mm = atoi(optarg);
break;
case 'h':
case '?':
default:
usage(argv[0]);
}
}
argc -= optind;
argv += optind;
if (argc == 0) {
in = stdin;
inname = "STDIN";
if (outname == NULL) {
out = stdout;
outname = "STDOUT";
}
}
else {
inname = argv[0];
if ((in = fopen(inname, "r")) == NULL) {
fprintf(stderr, "Couldn't open %s for reading: %s\n",
inname, strerror(errno));
exit(1);
}
if (outname == NULL) {
outname = malloc(strlen(inname) + 5);
strcpy(outname, inname);
for (i = strlen(outname); i >= 0; --i)
if (outname[i] == '.') break;
if (i >= 0)
strcpy(outname + i + 1, "raw");
else
strcpy(outname + strlen(outname), ".raw");
}
}
if ((out == NULL) && ((out = fopen(outname, "w")) == NULL)) {
fprintf(stderr, "Couldn't open %s for writing: %s\n",
outname, strerror(errno));
exit(1);
}
pt_pack(in, out, wheel_sz_mm, rec_int);
fclose(in);
fclose(out);
return 0;
}

152
ptunpk.c Normal file
View File

@@ -0,0 +1,152 @@
/*
* $Id: ptunpk.c,v 1.4 2006/06/04 14:32:34 srhea Exp $
*
* Copyright (c) 2006 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 <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pt.h"
#define KM_TO_MI 0.62137119
#define BAD_KM_TO_MI 0.62
static FILE *out;
static void
config_cb(unsigned interval, unsigned rec_int, unsigned wheel_sz_mm,
void *context)
{
context = NULL;
fprintf(out, "# wheel size=%d mm, interval=%d, rec int=%d\n",
wheel_sz_mm, interval, rec_int);
}
static void
time_cb(struct tm *time, time_t since_epoch, void *context)
{
context = NULL;
fprintf(out, "# %d/%d/%d %d:%02d:%02d %d\n",
time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec, (int) since_epoch);
}
static void
data_cb(double secs, double nm, double mph, double watts, double miles,
unsigned cad, unsigned hr, unsigned interval, void *context)
{
context = NULL;
fprintf(out, "%.3f %.1f", secs / 60.0, nm);
if (mph == -1.0)
fprintf(out, " NaN NaN");
else
fprintf(out, " %0.3f %.0f", mph, watts);
fprintf(out, " %.5f %d", miles, cad);
if (hr == 0)
fprintf(out, " NaN");
else
fprintf(out, " %d", hr);
fprintf(out, " %d\n", interval);
}
static void
error_cb(const char *msg, void *context)
{
context = NULL;
fprintf(stderr, "%s\n", msg);
exit(1);
}
static void
usage(const char *progname)
{
fprintf(stderr, "usage: %s [-c] [-o <output file>] [<input file>]\n",
progname);
exit(1);
}
int
main(int argc, char *argv[])
{
int ch, i, compat = 0;
char *inname = NULL, *outname = NULL;
FILE *in;
while ((ch = getopt(argc, argv, "cho:")) != -1) {
switch (ch) {
case 'c':
compat = 1;
break;
case 'o':
outname = optarg;
if (strcmp(outname, "-") == 0) {
out = stdout;
outname = "STDOUT";
}
break;
case 'h':
case '?':
default:
usage(argv[0]);
}
}
argc -= optind;
argv += optind;
if (argc == 0) {
in = stdin;
inname = "STDIN";
if (outname == NULL) {
out = stdout;
outname = "STDOUT";
}
}
else {
inname = argv[0];
if ((in = fopen(inname, "r")) == NULL) {
fprintf(stderr, "Couldn't open %s for reading: %s\n",
inname, strerror(errno));
exit(1);
}
if (outname == NULL) {
outname = malloc(strlen(inname) + 5);
strcpy(outname, inname);
for (i = strlen(outname); i >= 0; --i)
if (outname[i] == '.') break;
if (i >= 0)
strcpy(outname + i + 1, "dat");
else
strcpy(outname + strlen(outname), ".dat");
}
}
if ((out == NULL) && ((out = fopen(outname, "w")) == NULL)) {
fprintf(stderr, "Couldn't open %s for writing: %s\n",
outname, strerror(errno));
exit(1);
}
fprintf(out, "# Time Torq MPH Watts Miles Cad HR Int\n");
pt_read_raw(in, compat, NULL, config_cb, time_cb, data_cb, error_cb);
return 0;
}

53
smooth.pl Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/perl -w
#
# $Id: smooth.pl,v 1.1 2006/05/16 14:24:50 srhea Exp $
#
# Copyright (c) 2006 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
#
use strict;
if ($#ARGV < 0) {
print "usage: smooth.pl <window length in seconds>\n";
exit 1;
}
my $len = $ARGV[0] / 60.0;
my $count = 0;
my $total = 0;
my $start_time = 0;
while (<STDIN>) {
if (m/^#/) {
}
else {
my @cols = split;
if (!($cols[1] eq "NaN")) {
++$count;
$total += $cols[1];
}
if ($cols[0] >= $start_time + $len) {
if ($count > 0) {
printf "$start_time %f\n", $total / $count;
}
$start_time = $cols[0];
$total = 0;
$count = 0;
}
}
}

30
test.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/sh
#
# $Id: test.sh,v 1.2 2006/05/14 14:17:21 srhea Exp $
#
# Copyright (c) 2006 Russ Cox (rsc@swtch.com) and Sean 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
#
grep 'Rider' $1.xml | sed -e 's/<[^>]*>/ /g' > win
if ! ./ptunpk -c < $1.raw | grep -v '^#' > mac
then
echo "Failed to unpack $1.raw";
exit 1
fi
./compare_rows.pl win mac $2
rm win mac