This document I found in the internet when I had a look for a description about the HAC4 file format. So my special thanks go to: Steffel Pingel The location where I found the file: http://members.tripod.de/SteffenPingel/palm/tourviewer/FILEFORMAT.txt I included some more facts. These are marked with "(added)". ******************************************************************************* The paper by Steffen Pingel: INTRODUCTION Observations of the CCHAC4 and CM414M (only parts) data format. NOTES This is all information I know about the data format of the CCHAC4. All this information was extracted with the help of a hex editor and close observation. This description is incomplete and at worst incorrect. Use it at your own risk! There are a few thing I can't quite understand. As a matter of fact storing all numbers as ASCII charactes wastes half of the space. This way each byte (8-bit) only stores a 4-bit value. Furthermore it seems a bit overblown to use 20% of the file for stop bytes (the checksum would probably do the job). IMHO transfers could be speed up so battery usage could be significantly reduced if the data was stored more packed. If you take a base converter and a calculator you might find that some of your computed values will vary to those displayed by software. This is because common software programms may aproximate values. In example the temperature is only saved every 2 minutes. You wouldn't normally expect temperature to change rapidlyso this is fine, but if it does software programms might compute intermediate values. Important contribution about missing fields in the file header and where the offsets are stored and computed where made by Dirk F. Raetzel. An implementation of software that can evaluates CCHAC4 raw data can be downloaded at http://members.tripod.de/SteffenPingel/palm/tourviewer/. If you have further questions or want to contribute to this document, feel free to contact me. Steffen Pingel PHYSIKAL PROPERTIES 9600 bits per second, 8 data bits, no parity, 1 stop bit. GENERAL Each file has a size of 81930 bytes and starts with four letters "AFRO". Every fifth byte is used as a stop byte, it has the value 0Dh (ASCII carriage return). Actually every single byte is stored as an ASCII character which then has to be converted to numbers. Depending on the type of data, some values are decimals and others are hex-decimals. In example the number 20 would be stored as 32h 30h where the 2 is represendted by 32h (50) which is the ASCII value of "2" and 0 is represented by 30h (48) which is equivalent to "0". The letters comtained in hex-decimals can be either stored lower-case or upper-case. In example C0h could be either represented by 43h 30h or 66h 30h. The last five bytes of each file make up the checksum. The checksum is calculated by adding up all 16384 4-byte hex-decimals and modulating them with FFFFh. DATA Subtracting the signature and the checksum leaves 81920 bytes of data. This data is divided into records a 40 byte. These 40 bytes contain 8 4-byte numbers (the other 8 bytes are used for stop bytes). This means the unit can store 2048 records. Each file starts with 16 records (right after the signature, offset 5) which I don't have figured out, yet. Most of these records contain 5555h anyway. A few words about the syntax of the following paragraphs. The first column contains the absolute decimal file offset or the device type. The second column contains a symbolic description of the record (???? = don't know, . = stop byte). The symbols are explained below the record description (t = text, d = decimal, h = hex-decimal, o = offset). The offsets used in the data files differ from those used in this text. The data file offsets are hex-decimals that count the number of data words up to the offset, ignoring the stop bytes and signature. They can be converted to absolute decimal file offsets by multiplication with 2.5 and addition of 5. FILEHEADER --------------------------------------------------------------------------- 0 ssss. ssss t "AFRO" signature (AFRO-Fluginstrumente GmbH) --------------------------------------------------------------------------- 5+ ????.????.????.????.????.????.????.????. don't know, yet (mostly 5555) --------------------------------------------------------------------------- 645 HAC4 cccc.pppp.wwww.aaaa.bbbb.cccc.dddd.eeee cccc h "B735" pppp h wheel perimeter (mm) wwww h ? weight (kg) aaaa h home altitude (m) "FFFF" not set bbbb h 1. pulse upper bound (bpm) cccc h 1. pulse lower bound (bpm) dddd h 2. pulse upper bound (bpm) eeee h 2. pulse lower bound (bpm) CM414M cccc.pppp.qqqq.????.aaaa.wwww.mmdd.yyyy cccc h "B723" (CM414M) pppp h ? 1. wheel perimeter (mm) qqqq h ? 2. wheel perimeter (mm) ???? h ? aaaa h home altitude (m) "FFFF" not set wwww h weight (kg) mm d month of transfer dd d day of transfer yyyy d year of transfer --------------------------------------------------------------------------- 685 HAC4 aabb.ccdd.????.????.kkkk.oooo.yyyy.mmdd aa d 1. count down minutes bb d 1. count down seconds cc d 2. count down minutes dd d 2. count down seconds ???? h "FF??" ???? h "0000" kkkk h total distance at end of last tour (km) oooo h next free memory offset yyyy d year of transfer mm d month of transfer dd d day of transfer CM414M ????.????.oooo.dddd.uuuu.UUUU.????.???? oooo h next free memory offset dddd o offset of last DD-record uuuu h total altitude up at end of last tour of bike 1 (m) (added) UUUU h total altitude up at end of last tour of bike 2 (m) (added) --------------------------------------------------------------------------- 725 HAC4 uuuu.dddd.aaaa.hh??.??mm.cccc.ddddd.???? uuuu h total altitude up at end of last tour (m) dddd h total altitude down at end of last tour (m) aaaa h max altitude (m) hh d hour of total travel time ?? ? ? ?? ? ? mm d minute of total travel time cccc o offset of last CC-record dddd o offset of last DD-record ???? ? "0000" "6280" "0130" CM414M ????.????.kkkk.llll.mmss.nntt.hh??.ii?? kkkk h 1. total distance at end of last tour (km) llll h 2. total distance at end of last tour (km) mm d 1. minute of total travel time ss d 1. second of total travel time nn d 2. minute of total travel time tt d 2. second of total travel time hh d 1. hour of total travel time ii d 2. hour of total travel time --------------------------------------------------------------------------- TOURDATA The tour data is stored as an endless chain. If the end is reached the unit begins at the start and overwrites the data. The tour data always has a record type that specifies what kind of data is stored. The type is stored in bytes three and four as a hex-decimal value. A new tour is introduced by a AA-record (byte three and four have the value Ah). The tour data is made up by BB-records and the tour end is marked by a CC- and a DD-record. --------------------------------------------------------------------------- 765+ ttTT.oooo.HHMM.mmdd.tttt.????.aaaa.pppp. TT h "AA" (tour start) tt h type of tour "2E" bike2 (CM414M) "3E" bike1 (CM414M) "81" jogging "91" ski "A1" bike "B1" ski-bike "0E" jogging (CM414M) (added) oooo o DD-record offset HH d hour of tour MM d minute of tour mm d month of tour dd d day of tour tttt h total distance at tour start (km) ???? "0000" aaaa h initial altitude (m) pppp h initial pulse (bpm) --------------------------------------------------------------------------- 765+ ttTT.mmcc.1111.2222.3333.4444.5555.6666. TT h "BB" (tour data) tt h temperature (°C) mm h marker (s) cc h cadence (rpm) 1111 - 6666 h values --------------------------------------------------------------------------- 765+ ttTT.mmcc.1111.2222.3333.4444.5555.6666. TT h "CC" (last tour data) tt h temperature (°C) mm h marker when recording exactly ended (s) cc h cadence (rpm) 1111 - 6666 h values --------------------------------------------------------------------------- 765+ ttTT.oooo.cccc.cccc.cccc.cccc.cccc.cccc. TT h "DD" (tour end) oooo o AA-record offset cccc h "0000" --------------------------------------------------------------------------- The year information seems to be missing in the tour start record. Observations of false intepreted files by the reference software confirm this. The only way to guess a tour's year is to start with the file's year, go through all tours by offset and to decrease the year when the month of a tour is bigger than the one of the previos tour. It seems kind of odd, since there are 16-bit spare bits in every tour start record... FILEEND --------------------------------------------------------------------------- 81925 cccc. cccc h checksum --------------------------------------------------------------------------- One very interesting detail was left out until now, how the values are stored. The AA-records store the initial values as hex-decimal numbers. The BB-records store the temperature and cadence and the time when a marker was set (relative to the record-time). The record-time is calculated by the position of the record relative to the start-record of the tour. Each record contains six values which are stored every 20 seconds so it contains information about a period of two minutes. Each value contains information about the pulse, altitude and distance. The values are stored relative to the previous value (if the pulse drops below 0, it is computed as 0 though). Since there are only 4 * 4 byte to store three numbers, range is limited. value width range factor (bit) --------------------------------------------------------------------------- pulse (bpm) 4 -8/+7 x * 2 altitude (m) 6 -32/+31 -16 <= x <= 16 : x x > 16 : 16 + (x - 16) * 7 x < -16 : -16 + (x + 16) * 7 distance (m) 6 +63 x * 10 Here is a sample c-function to compute these values (from tourviewer.c): /* * disassembles a data record * */ void WordToDataRec(Word value, DataRecPtr dataRecPtr) { // pulse (4) if (value & 0x8000) //dataRecPtr->pulse = -(16 - ((value & 0x7000) >> 12)) * 2; dataRecPtr->pulse = (0xFFF0 | ((value & 0xF000) >> 12)) * 2; else dataRecPtr->pulse = ((value & 0xF000) >> 12) * 2; // altitude (6) if (value & 0x0800) { //dataRecPtr->altitude = 32 - ((value & 0x07C0) >> 6); dataRecPtr->altitude = 0xFFC0 | ((value & 0x0FC0) >> 6); if (dataRecPtr->altitude < -16) dataRecPtr->altitude = -16 + ((dataRecPtr->altitude + 16) * 7); } else { dataRecPtr->altitude = ((value & 0x0FC0) >> 6); if (dataRecPtr->altitude > 16) dataRecPtr->altitude = 16 + ((dataRecPtr->altitude - 16) * 7); } // distance (6) dataRecPtr->distance = value & 0x003F; } COPYRIGHT NOTES All brand and product names are trademarks or registered trademarks of their respective holders. Copyright (C) 2000-2001 by Steffen Pingel. Last change: 2001-03-03