1 package net.sf.tourviewer.lib.ciclo;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.PrintStream;
6 import java.util.Calendar;
7 import java.util.Date;
8 import net.sf.tourviewer.lib.Bike;
9 import net.sf.tourviewer.lib.Marker;
10 import net.sf.tourviewer.lib.Tour;
11 import net.sf.tourviewer.lib.TourRecord;
12 import net.sf.tourviewer.lib.TourSet;
13
14 public class CicloRawReader {
15
16 private static final int BITS_PER_WORD = 16;
17 private static final int BLOCK_COUNT = 2048;
18 private static final int BLOCK_SIZE = 8;
19
20 /***
21 * Time interval for which a data block stores its values in s.
22 */
23 private static final int DATA_RECORD_INTERVAL = 20;
24 private static final int DATA_RECORDS_PER_BLOCK = 6;
25 private static final String FILE_SIGNATURE = "AFRO";
26 private static final int LAST_INDEX = BLOCK_COUNT - 1;
27
28 private static final int WORDS_PER_BLOCK = 16;
29
30 private Block[] blocks;
31 private int checksum;
32 private CicloDevice device;
33 private int expectedChecksum;
34
35 public boolean accept(InputStream in) throws IOException
36 {
37 byte[] buffer = new byte[FILE_SIGNATURE.length()];
38 in.mark(buffer.length);
39 in.read(buffer);
40
41 try {
42 return FILE_SIGNATURE.equalsIgnoreCase(new String(buffer));
43 }
44 finally {
45 in.reset();
46 }
47 }
48
49 private TourRecord addTourRecords(Calendar cal, Tour tour, TourRecord prevRecord, DataBlock[] data, int endMarker, int cadence, int temperature)
50 {
51 int count = endMarker / DATA_RECORD_INTERVAL + 1;
52 if (endMarker % DATA_RECORD_INTERVAL == 0) {
53 count--;
54 }
55
56
57 for (int i = 0; i < count; i++) {
58 TourRecord record = new TourRecord();
59 record.setCadence(cadence);
60 record.setTemperature(temperature);
61 record.setAltitude(prevRecord.getAltitude() + data[i].getAltitudeDelta());
62 record.setDistance(prevRecord.getDistance() + data[i].getDistanceDelta() * 10);
63 record.setPulse(Math.max(prevRecord.getPulse() + data[i].getPulseDelta(), 0));
64 if (i < count - 1 || endMarker % DATA_RECORD_INTERVAL == 0) {
65 cal.add(Calendar.SECOND, DATA_RECORD_INTERVAL);
66 }
67 else {
68 cal.add(Calendar.SECOND, endMarker % DATA_RECORD_INTERVAL);
69 }
70 record.setTime(cal.getTime());
71 tour.addRecord(record);
72 prevRecord = record;
73 }
74 return prevRecord;
75 }
76
77
78 private Date adjustDate(Calendar cal, Date lastDate, int year)
79 {
80 if (lastDate != null) {
81 Calendar cal2 = Calendar.getInstance();
82 cal2.setTime(lastDate);
83 int lastYear = cal2.get(Calendar.YEAR);
84 cal.set(Calendar.YEAR, lastYear);
85 if (lastDate.before(cal.getTime())) {
86 cal.set(Calendar.YEAR, lastYear - 1);
87 }
88 return cal.getTime();
89 }
90 else {
91 cal.set(Calendar.YEAR, year);
92 return cal.getTime();
93 }
94 }
95
96 private TourSet analyze(int[] data)
97 {
98 if (data.length != BLOCK_COUNT * WORDS_PER_BLOCK) {
99 throw new IllegalArgumentException("Expected data.length == " + BLOCK_COUNT * WORDS_PER_BLOCK);
100 }
101
102 blocks = new Block[BLOCK_COUNT];
103 if (data[128] == HAC4_315.MAGIC) {
104 device = new HAC4_315();
105 return analyzeHAC4(data);
106 }
107 if (data[128] == HAC4_Imp.MAGIC) {
108 device = new HAC4_Imp();
109 return analyzeHAC4(data);
110 }
111 else if (data[128] == CM414M.MAGIC) {
112 device = new CM414M();
113 return analyzeCM414M(data);
114 }
115 else {
116 device = new HAC4_325();
117 return analyzeHAC4_325(data);
118 }
119 }
120
121 private TourSet analyzeCM414M(int[] data)
122 {
123 final int index = device.getInfoBlockIndex();
124 if (index == CicloDevice.NO_INDEX) {
125 throw new IllegalArgumentException("device.getInfoBlockIndex() must be valid");
126 }
127
128
129 blocks[index] = new CM414MInfo1Block(getRecordData(data, index));
130 blocks[index + 1] = new CM414MInfo2Block(getRecordData(data, index + 1));
131 blocks[index + 2] = new CM414MInfo3Block(getRecordData(data, index + 2));
132 analyzeTourData(data);
133
134
135 TourSet tourSet = new TourSet();
136 tourSet.setDevice(device);
137
138 CM414MInfo1Block info1Record = (CM414MInfo1Block)blocks[index];
139 tourSet.setPersonWeight(info1Record.getWeight());
140
141 CM414MInfo2Block info2Record = (CM414MInfo2Block)blocks[index + 1];
142 CM414MInfo3Block info3Record = (CM414MInfo3Block)blocks[index + 2];
143
144 Bike bike = new Bike();
145 bike.setDistance(info3Record.getTotalDistance1());
146 bike.setTravelTime(info3Record.getTotalTravelTime1());
147 bike.setWheelPerimeter(info1Record.getWheelPerimeter1());
148 tourSet.addBike(bike);
149
150 bike = new Bike();
151 bike.setDistance(info3Record.getTotalDistance2());
152 bike.setTravelTime(info3Record.getTotalTravelTime2());
153 bike.setWheelPerimeter(info1Record.getWheelPerimeter2());
154 tourSet.addBike(bike);
155
156 parseTourRecords(tourSet, info2Record.getLastDDOffset(), info1Record.getYear());
157
158 return tourSet;
159 }
160
161 private TourSet analyzeHAC4(int[] data)
162 {
163 final int index = device.getInfoBlockIndex();
164 if (index == CicloDevice.NO_INDEX) {
165 throw new IllegalArgumentException("device.getInfoBlockIndex() must be valid");
166 }
167
168
169 blocks[index] = new HAC4Info1Block(getRecordData(data, index));
170 blocks[index + 1] = new HAC4Info2Block(getRecordData(data, index + 1));
171 blocks[index + 2] = new HAC4Info3Block(getRecordData(data, index + 2));
172 analyzeTourData(data);
173
174
175 TourSet tourSet = new TourSet();
176 tourSet.setDevice(device);
177
178 HAC4Info1Block info1Record = (HAC4Info1Block)blocks[index];
179 tourSet.setPersonWeight(info1Record.getWeight());
180
181 HAC4Info2Block info2Record = (HAC4Info2Block)blocks[index + 1];
182 HAC4Info3Block info3Record = (HAC4Info3Block)blocks[index + 2];
183
184 Bike bike = new Bike();
185 bike.setDistance(info2Record.getTotalDistance());
186 bike.setTravelTime(info3Record.getTotalTravelTime());
187 bike.setWheelPerimeter(info1Record.getWheelPerimeter());
188 tourSet.addBike(bike);
189
190 parseTourRecords(tourSet, info3Record.getLastDDOffset(), info2Record.getYear());
191
192 return tourSet;
193 }
194
195 private TourSet analyzeHAC4_325(int[] data)
196 {
197 TourSet tourSet = new TourSet();
198 tourSet.setDevice(device);
199
200
201
202 return tourSet;
203 }
204
205 protected void analyzeTourData(int[] data)
206 {
207 for (int i = device.getTourBlockIndex(); i < BLOCK_COUNT; i++) {
208 int[] recordData = getRecordData(data, i);
209 int type = recordData[0] & 0xFF;
210 if (type == 0xAA) {
211 blocks[i] = new AABlock(recordData);
212 }
213 else if (type == 0xBB) {
214 blocks[i] = new BBBlock(recordData);
215 }
216 else if (type == 0xCC) {
217 blocks[i] = new CCBlock(recordData);
218 }
219 else if (type == 0xDD) {
220 blocks[i] = new DDBlock(recordData);
221 }
222 else {
223 blocks[i] = new UnknownBlock(recordData);
224 }
225 }
226 }
227
228 private int decTourIndex(int index)
229 {
230 return (index == device.getTourBlockIndex()) ? LAST_INDEX : index - 1;
231 }
232
233 public int getChecksum()
234 {
235 return checksum;
236 }
237
238 public int getExpectedChecksum()
239 {
240 return expectedChecksum;
241 }
242
243 private int[] getRecordData(int[] data, int index)
244 {
245 int[] recordData = new int[BLOCK_SIZE];
246 System.arraycopy(data, index * BLOCK_SIZE, recordData, 0, BLOCK_SIZE);
247 return recordData;
248 }
249
250 private int incTourIndex(int index)
251 {
252 return (index == LAST_INDEX) ? device.getTourBlockIndex() : index + 1;
253 }
254
255 private int offsetToIndex(int offset)
256 {
257 return offset / WORDS_PER_BLOCK;
258 }
259
260 private Tour parseTour(AABlock startRecord, int startIndex, Date lastDate, int year)
261 {
262 Calendar cal = Calendar.getInstance();
263
264 Tour tour = new Tour();
265 tour.setAutoStatistics(true);
266
267 cal.set(Calendar.MONTH, startRecord.getTimeMonth() - 1);
268 cal.set(Calendar.DAY_OF_MONTH, startRecord.getTimeDay());
269 cal.set(Calendar.HOUR_OF_DAY, startRecord.getTimeHour());
270 cal.set(Calendar.MINUTE, startRecord.getTimeMinute());
271 tour.setStartTime(adjustDate(cal, lastDate, year));
272
273 TourRecord prevRecord = new TourRecord();
274 prevRecord.setAltitude(startRecord.getInitialAltitude());
275 prevRecord.setDistance(0);
276 prevRecord.setPulse(startRecord.getInitialPulse());
277 prevRecord.setTime(tour.getStartTime());
278 tour.addRecord(prevRecord);
279
280 boolean valid = false;
281 int index = startIndex;
282 while (true) {
283 index = incTourIndex(index);
284 if (blocks[index] instanceof BBBlock) {
285 BBBlock dataRecord = (BBBlock)blocks[index];
286 if (!valid) {
287 valid = true;
288
289
290 prevRecord.setTemperature(dataRecord.getTemperature());
291 }
292 if (dataRecord.getMarker() != 0) {
293 cal.add(Calendar.SECOND, dataRecord.getMarker());
294 tour.addMarker(new Marker(cal.getTime()));
295 cal.add(Calendar.SECOND, -dataRecord.getMarker());
296 }
297 prevRecord = addTourRecords(cal, tour, prevRecord,
298 dataRecord.getDataRecords(), DATA_RECORD_INTERVAL * DATA_RECORDS_PER_BLOCK,
299 dataRecord.getCadence(), dataRecord.getTemperature());
300 }
301 else if (blocks[index] instanceof CCBlock) {
302 if (!valid) {
303
304 return null;
305 }
306
307 CCBlock dataRecord = (CCBlock)blocks[index];
308 cal.add(Calendar.SECOND, dataRecord.getEndMarker());
309 tour.setEndTime(cal.getTime());
310 cal.add(Calendar.SECOND, -dataRecord.getEndMarker());
311 prevRecord = addTourRecords(cal, tour, prevRecord,
312 dataRecord.getDataRecords(), dataRecord.getEndMarker(),
313 dataRecord.getCadence(), dataRecord.getTemperature());
314 tour.setDistance(prevRecord.getDistance());
315
316 index = incTourIndex(index);
317 if (blocks[index] instanceof DDBlock) {
318 DDBlock endBlock = (DDBlock)blocks[index];
319 return tour;
320 }
321 else {
322 return null;
323 }
324 }
325 else {
326 return null;
327 }
328 }
329 }
330
331 private void parseTourRecords(TourSet tourSet, int lastDDOffset, int yearOfTransfer)
332 {
333 Bike[] bikes = tourSet.getBikes();
334 if (bikes == null) {
335 throw new IllegalArgumentException("Bikes may not be null or empty");
336 }
337
338 int index = offsetToIndex(lastDDOffset);
339 int startIndex = index;
340 int lastIndex = index;
341 Date lastDate = null;
342
343 while ((index < startIndex)
344 ? (index < lastIndex)
345 : (unwrapIndex(index) < startIndex && unwrapIndex(index) < lastIndex)) {
346 if (blocks[index] instanceof DDBlock) {
347 DDBlock endRecord = (DDBlock)blocks[index];
348
349 index = offsetToIndex(endRecord.getAAOffset());
350 if (blocks[index] instanceof AABlock) {
351 AABlock startBlock = (AABlock)blocks[index];
352 Tour tour = parseTour(startBlock, index, lastDate, yearOfTransfer);
353 if (tour != null) {
354 lastDate = tour.getStartTime();
355 setTourType(tour, startBlock, bikes);
356 tourSet.addTour(tour);
357 }
358 lastIndex = (index < startIndex) ? index : unwrapIndex(index);
359 index = decTourIndex(index);
360 }
361 else {
362 return;
363 }
364 }
365 else {
366 return;
367 }
368 }
369 }
370
371 public void print(PrintStream out)
372 {
373 for (int i = 0; i < blocks.length; i++) {
374 System.out.printf("%04d: ", i);
375 if (blocks[i] != null) {
376 out.println(blocks[i]);
377 }
378 else {
379 out.println();
380 }
381 }
382 }
383
384 public TourSet read(InputStream in) throws IOException {
385 int lastValue = 0;
386 byte[] buffer = new byte[5];
387 int[] data = new int[BLOCK_COUNT * WORDS_PER_BLOCK];
388 int offset = 0;
389
390 in.read(buffer);
391 if (!FILE_SIGNATURE.equalsIgnoreCase(new String(buffer, 0, 4))) {
392 throw new IOException("Invalid file signature, expected " + FILE_SIGNATURE);
393 }
394
395 while (in.read(buffer) != -1) {
396 checksum = (checksum + lastValue) & 0xFFFF;
397
398 String bufferText = new String(buffer, 0, 4);
399 lastValue = Integer.parseInt(bufferText, 16);
400
401 if (offset < data.length) {
402 data[offset] = lastValue;
403 }
404 else if (offset > data.length) {
405 throw new IOException("File is too long, expected 81930 byte");
406 }
407 offset ++;
408 }
409
410 expectedChecksum = lastValue;
411 return analyze(data);
412 }
413
414 private void setTourType(Tour tour, AABlock startBlock, Bike[] bikes)
415 {
416 AABlock.TourType type = startBlock.getTourType();
417 if (type == AABlock.TourType.BIKE || type == AABlock.TourType.BIKE1) {
418 tour.setType(Tour.Type.BIKE);
419 if (bikes.length >= 1) {
420 tour.setBike(bikes[0]);
421 }
422 }
423 else if (type == AABlock.TourType.SKI_BIKE) {
424 tour.setType(Tour.Type.SKI_BIKE);
425 if (bikes.length >= 1) {
426 tour.setBike(bikes[0]);
427 }
428 }
429 else if (type == AABlock.TourType.BIKE2) {
430 tour.setType(Tour.Type.BIKE);
431 if (bikes.length >= 2) {
432 tour.setBike(bikes[1]);
433 }
434 }
435 else if (type == AABlock.TourType.JOGGING) {
436 tour.setType(Tour.Type.JOGGING);
437
438 }
439 }
440
441 private int unwrapIndex(int index)
442 {
443 return device.getTourBlockIndex() - (LAST_INDEX - index);
444 }
445
446 }