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