1 /*
2     Handle energympro (GPS training watch) .cpo files
3 
4     Copyright (c) 2014 Zingo Andersen zingo@vectrace.com
5     Copyright (C) 2014 Robert Lipe, robertlipe+source@gpsbabel.org
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 
21  */
22 
23 #include <cstdint>              // for int32_t
24 #include <cstdio>               // for printf, SEEK_SET, SEEK_CUR, SEEK_END
25 
26 #include <QtCore/QDate>         // for QDate
27 #include <QtCore/QDateTime>     // for QDateTime
28 #include <QtCore/QDebug>        // for QDebug
29 #include <QtCore/QString>       // for QString
30 #include <QtCore/QTime>         // for QTime
31 #include <QtCore/QTimeZone>     // for QTimeZone
32 #include <QtCore/Qt>            // for UTC
33 
34 #include "defs.h"
35 #include "energympro.h"
36 #include "gbfile.h"             // for gbfgetc, gbfseek, gbfclose, gbfopen, gbfread, gbfgetuint32, gbfcopyfrom, gbfgetuint16, gbfile, gbsize_t
37 #include "src/core/datetime.h"  // for DateTime
38 
39 
40 #define MYNAME "energympro"
41 
42 //*******************************************************************************
43 //           local helper functions
44 //*******************************************************************************
45 void
read_point(route_head * gpsbabel_route,gpsbabel::DateTime & gpsDateTime) const46 EnergymproFormat::read_point(route_head* gpsbabel_route, gpsbabel::DateTime& gpsDateTime) const
47 {
48   tw_point point{};
49   gbfread(&point, sizeof(tw_point), 1, file_in);
50   if (global_opts.debug_level > 1) {
51     printf("Point: lat:%8u long:%8u alt:%8d ", point.Latitude, point.Longitude, point.Altitude);
52     printf("speed:%6u dist:%5u time:%5u Status:%1u", point.Speed, point.IntervalDist, point.lntervalTime, point.Status);
53     printf("HR:(%3d,%1d)", point.HR_Heartrate, point.HR_Status);
54     printf("Speed:(%8u,%1d)", point.Speed_Speed, point.Speed_Status);
55     printf("Cad:(%3d,%1d)", point.Cadence_Cadence, point.Cadence_Status);
56     printf("Power (Cad:%6d Pow:%6d,%2d)Temp:%3d\n", point.Power_Cadence, point.Power_Power, point.Power_Status, point.Temp);
57 
58     qDebug() << "DateTime1:" << gpsDateTime.toString();
59     qDebug() << "point.lntervalTime:" << point.lntervalTime;
60   }
61 
62   //Time from last point in sec's * 10 (e.g. point.lntervalTime is sec multiplied with 10)
63   // convert to millisecs
64   gpsDateTime = gpsDateTime.addMSecs(point.lntervalTime*100);
65 
66   auto waypt = new Waypoint;
67   waypt->latitude = (point.Latitude / 1000000.0);
68   waypt->longitude = (point.Longitude / 1000000.0);
69   waypt->altitude = point.Altitude;
70 
71   if (global_opts.debug_level > 1) {
72     qDebug() << "DateTime2:" << gpsDateTime.toString();
73   }
74 
75   waypt->SetCreationTime(gpsDateTime);
76 
77   if (point.Speed_Status == 0) {
78     WAYPT_SET(waypt, speed, point.Speed_Speed / 100.0f);
79   }
80   if (point.HR_Status == 0) {
81     waypt->heartrate = point.HR_Heartrate;
82   }
83   if (point.Cadence_Status == 0) {
84     waypt->cadence = point.Cadence_Cadence;
85   }
86   if (point.Power_Status == 0) {
87     waypt->power = point.Power_Power;
88   }
89   WAYPT_SET(waypt, temperature, point.Temp);
90   track_add_wpt(gpsbabel_route, waypt);
91 }
92 
93 
94 void
read_lap() const95 EnergymproFormat::read_lap() const
96 {
97   tw_lap lap{};
98   gbfread(&lap, sizeof(tw_lap), 1, file_in);
99   if (global_opts.debug_level > 1) {
100     printf("LAP: splitTime:%6us TotalTime:%6us LapNumber:%5d ", lap.splitTime/10, lap.TotalTime/10, lap.Number);
101     printf("dist:%08um Cal:%5u Speed:(%6u,%6u) ", lap.lDistance, lap.Calorie, lap.MaxSpeed, lap.AvgSpeed);
102     printf("HR:(%3d,%3d)", lap.MaxHeartrate, lap.AvgHeartrate);
103     printf("Alt:(%6d,%6d) ", lap.MinAlti, lap.MaxAlti);
104     printf("Cad:(%3d,%3d) ", lap.AvgCad, lap.MaxCad);
105     printf("Power:(%3d,%3d)w ", lap.AvgPower, lap.MaxPower);
106     printf("Pt:(%6d,%6d)\n", lap.StartRecPt, lap.FinishRecPt);
107   }
108 }
109 
110 //*******************************************************************************
111 //           global callbacks called by gpsbabel main process
112 //*******************************************************************************
113 
114 void
rd_init(const QString & fname)115 EnergymproFormat::rd_init(const QString& fname)
116 {
117   if (global_opts.debug_level > 1) {
118     printf(MYNAME " rd_deinit()\n");
119   }
120   gbfile* fileorg_in = gbfopen(fname, "rb", MYNAME);
121 
122   /* copy file to memory stream (needed for seek-ops and piped commands) */
123   file_in = gbfopen(nullptr, "wb", MYNAME);
124   gbsize_t size = gbfcopyfrom(file_in, fileorg_in, 0x7FFFFFFF);
125   if (global_opts.debug_level > 1) {
126     printf(MYNAME "  filesize=%u\n", size);
127   }
128   gbfclose(fileorg_in);
129   if (opt_timezone) {
130     if (QTimeZone::isTimeZoneIdAvailable(opt_timezone)) {
131       timezn = new QTimeZone(opt_timezone);
132     } else {
133       list_timezones();
134       fatal(MYNAME ": Requested time zone \"%s\" is not available.\n", opt_timezone);
135     }
136   } else {
137     timezn = nullptr;
138   }
139 }
140 
141 void
rd_deinit()142 EnergymproFormat::rd_deinit()
143 {
144   if (timezn != nullptr) {
145     delete timezn;
146     timezn = nullptr;
147   }
148   if (global_opts.debug_level > 1) {
149     printf(MYNAME " rd_deinit()\n");
150   }
151   gbfclose(file_in);
152 }
153 
154 void
track_read()155 EnergymproFormat::track_read()
156 {
157   if (global_opts.debug_level > 1) {
158     printf(MYNAME "  waypoint_read()\n");
159   }
160 
161   gbfseek(file_in, 0L, SEEK_END);
162   gbfseek(file_in, -(int32_t)(sizeof(tw_workout)), SEEK_CUR);
163   tw_workout workout{};
164   workout.dateStart.Year = gbfgetc(file_in);
165   workout.dateStart.Month = gbfgetc(file_in);
166   workout.dateStart.Day = gbfgetc(file_in);
167   workout.timeStart.Hour = gbfgetc(file_in);
168   workout.timeStart.Minute = gbfgetc(file_in);
169   workout.timeStart.Second = gbfgetc(file_in);
170   workout.TotalRecPt = gbfgetuint16(file_in);
171   workout.TotalTime = gbfgetuint32(file_in);
172   workout.TotalDist = gbfgetuint32(file_in);
173   workout.LapNumber = gbfgetuint16(file_in);
174   workout.Calory = gbfgetuint16(file_in);
175   workout.MaxSpeed = gbfgetuint32(file_in);
176   workout.AvgSpeed = gbfgetuint32(file_in);
177   workout.MaxHeart = gbfgetc(file_in);
178   workout.AvgHeart = gbfgetc(file_in);
179 
180   if (global_opts.debug_level > 1) {
181     printf("%04d-%02d-%02d ", workout.dateStart.Year+2000, workout.dateStart.Month, workout.dateStart.Day);
182     printf("%02d:%02d:%02d ", workout.timeStart.Hour, workout.timeStart.Minute, workout.timeStart.Second);
183     printf("Total(RecPt:%6d Time:%6us Dist:%9um) LapNumber:%5d \n", workout.TotalRecPt, workout.TotalTime/10, workout.TotalDist, workout.LapNumber);
184   }
185 
186   /*
187    * GPS year: 2000+; struct tm year: 1900+
188    */
189   QDate gpsDate = QDate(workout.dateStart.Year+2000, workout.dateStart.Month, workout.dateStart.Day);
190   QTime gpsTime = QTime(workout.timeStart.Hour, workout.timeStart.Minute, workout.timeStart.Second);
191   gpsbabel::DateTime gpsDateTime;
192   if (timezn != nullptr) {
193     gpsDateTime = gpsbabel::DateTime(QDateTime(gpsDate, gpsTime, *timezn).toUTC());
194   } else {
195     gpsDateTime = gpsbabel::DateTime(QDateTime(gpsDate, gpsTime, Qt::LocalTime).toUTC());
196   }
197 
198   auto* gpsbabel_route = new route_head;
199 
200   track_add_head(gpsbabel_route);
201   gbfseek(file_in, 0L, SEEK_SET);
202 
203   for (int point=0; point<workout.TotalRecPt; point++) {
204     read_point(gpsbabel_route, gpsDateTime);
205   }
206 
207   gbfseek(file_in, sizeof(tw_point)*(workout.TotalRecPt), SEEK_SET);
208   for (int lap=0; lap<workout.LapNumber; lap++) {
209     read_lap();
210   }
211 }
212 
213 void
read()214 EnergymproFormat::read()
215 {
216   if (global_opts.debug_level > 1) {
217     printf(MYNAME " data_read()\n");
218   }
219 
220   track_read();
221 }
222