mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
initial import
This commit is contained in:
340
COPYING
Normal file
340
COPYING
Normal 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
53
Makefile
Normal 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
83
compare_rows.pl
Executable 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
55
cpint-cmd.c
Normal 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
251
cpint.c
Normal 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(®, "^([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(®, 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
29
cpint.h
Normal 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
42
doc/Makefile
Normal 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
5
doc/contact.content
Normal 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
18
doc/contrib.content
Normal 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
12
doc/cpint.gp
Normal 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
BIN
doc/cpint.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
43
doc/download.content
Normal file
43
doc/download.content
Normal 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
62
doc/faq.content
Normal 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<1=_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<1=_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
BIN
doc/gc_2006-05-16.tgz
Normal file
Binary file not shown.
BIN
doc/gc_2006-05-25.tgz
Normal file
BIN
doc/gc_2006-05-25.tgz
Normal file
Binary file not shown.
BIN
doc/gc_2006-05-27.tgz
Normal file
BIN
doc/gc_2006-05-27.tgz
Normal file
Binary file not shown.
BIN
doc/gc_2006-08-11.tgz
Normal file
BIN
doc/gc_2006-08-11.tgz
Normal file
Binary file not shown.
131
doc/genpage.pl
Executable file
131
doc/genpage.pl
Executable 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
BIN
doc/gui-preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
26
doc/index.content
Normal file
26
doc/index.content
Normal 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
68
doc/license.content
Normal 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
BIN
doc/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
doc/logo.png
Normal file
BIN
doc/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 229 B |
8
doc/sample.gp
Normal file
8
doc/sample.gp
Normal 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
BIN
doc/sample.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
42
doc/search.content
Normal file
42
doc/search.content
Normal 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
222
doc/users-guide.content
Normal 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
176
gui/AllPlot.cpp
Normal 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
67
gui/AllPlot.h
Normal 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
112
gui/ChooseCyclistDialog.cpp
Normal 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
53
gui/ChooseCyclistDialog.h
Normal 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
312
gui/DownloadRideDialog.cpp
Normal 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
72
gui/DownloadRideDialog.h
Normal 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
30
gui/GoldenCheetah.pro
Normal 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
279
gui/MainWindow.cpp
Normal 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
66
gui/MainWindow.h
Normal 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
100
gui/RawFile.cpp
Normal 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
58
gui/RawFile.h
Normal 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
138
gui/RideItem.cpp
Normal 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
42
gui/RideItem.h
Normal 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
36
gui/Settings.h
Normal 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
10
gui/TODO
Normal 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
13
gui/addqt.sh
Executable 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
69
gui/main.cpp
Normal 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
17
gui/mkdmg.sh
Executable 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
113
intervals.pl
Executable 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
75
notes.txt
Normal 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
8
package.sh
Executable 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
632
pt.c
Normal 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(®, "^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(®, 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(®_com, "^#", REG_EXTENDED | REG_NOSUB))
|
||||
assert(0);
|
||||
if (regcomp(®_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(®_com, line, 0, NULL, 0) == 0) {
|
||||
/* do nothing */
|
||||
}
|
||||
else if (regexec(®_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
98
pt.h
Normal 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
231
ptdl.c
Normal 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
229
ptpk.c
Normal 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
152
ptunpk.c
Normal 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
53
smooth.pl
Executable 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
30
test.sh
Executable 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
|
||||
|
||||
Reference in New Issue
Block a user