View Javadoc

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  		// TODO interpolate temperature
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 		// prepare records
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 		// parse records
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 		// prepare records
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 		// parse records
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 		// FIXME need to manually scan for tours
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 					// the first record always has the temperature of the
289 					// first data record
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 					// tour contains no data
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 }