A aviation & planes forum. AviationBanter

If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed. To start viewing messages, select the forum that you want to visit from the selection below.

Go Back   Home » AviationBanter forum » rec.aviation newsgroups » Piloting
Site Map Home Register Authors List Search Today's Posts Mark Forums Read Web Partners

JPI Data Format and Conversion



 
 
Thread Tools Display Modes
  #1  
Old June 10th 05, 11:53 PM
Josh
external usenet poster
 
Posts: n/a
Default JPI Data Format and Conversion

See http://www.geocities.com/joshhurtel/

  #2  
Old June 10th 05, 11:56 PM
Josh
external usenet poster
 
Posts: n/a
Default

//
// JPIHack.cpp : A command line application which can read JPI .DAT
files
// and output .CSV files. Or alternatively, can rewrite
the
// .DAT files into the older .DAT format so the JPI
EZSave
// program will continue to work on them, which is
helpful
// for any cases where this program doesn't recognize
some
// obscure part of the data files.
//
// Note that the .DAT file format is not completely known in a few
areas,
// in particular the following JPI configurations haven't been tested:
//
// - Configurations with more than 6 cylinders almost certainly don't
work
// - Twin configurations with some options, particularly fuel flow, are
untested
//
// Program originally built with MSVC++, but it's reasonably generic
C/C++
// which should be easily adaptable to other platforms without too much
// headache if one were so inclined.




/************************************************** *******
General notes on the format of JPI .DAT files
=============================================

The general format of the .DAT files is a number of textual header
records
(broken into lines with CR/LF), followed by binary data dumping each
flight's
data. There is a header record for each flight which stores how long
the
flight's data is.

Header records
--------------

Header records are "$X,....*NN\r\n" where X is a letter for a record
type, and
header records are just text lines delimited by line feeds. Most are
series of
numeric short values just converted to comma delimited ascii.

Header records all end in "*NN", where the NN is two hex digits. It is
a
checksum, computed by a byte which is the XOR of all the bytes in the
header
line excluding the initial '$' and trailing '*NN'.

There is also an end of file record following all of the flight data,
which
appears to be "$E,4*5D\r\n" in all cases seen. No useful meaning is
known.

Header record types
-------------------

Fields are in ascii, generally unsigned short values delimited by
commas:

$U = tail number
"$U,N12345_*44"

$A = configured limits:
VoltsHi*10,VoltsLo*10,DIF,CHT,CLD,TIT,OilHi,OilLo
"$A,305,230,500,415,60,1650,230,90*7F"

$F = Fuel flow config and limits
empty,full,warning,kfactor,kfactor
"$F,0,999, 0,2950,2950*53"

$T = timestamp of download, fielded (Times are UTC)
MM,DD,YY,hh,mm,?? maybe some kind of seq num but not strictly
sequential?
"$T, 5,13, 5,23, 2, 2222*65"

$C = config info (only partially known)
model#,feature flags lo, feature flags hi, unknown flags,firmware
version
"$C, 700,63741, 6193, 1552, 292*58"

The feature flags is a 32 bit set of flags as follows:

// -m-d fpai r2to eeee eeee eccc cccc cc-b
//
// e = egt (up to 9 cyls)
// c = cht (up to 9 cyls)
// d = probably cld
// b = bat
// o = oil
// t = tit1
// 2 = tit2
// a = OAT
// f = fuel flow
// r = CDT (also CARB - not distinguished in the CSV output)
// i = IAT
// m = MAP
// p = RPM
// *** e and c may be swapped (but always exist in tandem)
// *** d and b may be swapped (but seem to exist in tandem)
// *** m, p and i may be swapped among themselves, haven't seen
// enough independent examples to know for sure.

$D = flight info
flight#, length of flight's data in 16 bit words
"$D, 227, 3979*57"

$L = last header record
unknown meaning
"$L, 49*4D"


Binary data follows immediately after the CR/LF of the last record.

All binary data records are checksummed and the single byte checksum is
appended following each record. The checksum is a simple XOR of every
byte of
the data record for firmware versions before 3.00, and the negative of
a
simple sum of every byte of the data record for firmware versions after
3.00.

See calc_old_checksum and calc_new_checksum() functions

It should be noted that this is the ONLY change to the data format that
was
made when JPI decided to no longer provide software that translated the
..DAT
format to .CSV. In other words, they added no features they just
purposely
broke old software. Draw your own conclusions about the motivation for
this.


Binary Records
--------------

1. Flight information header


The flight header follows immediately after the $L record, and is as
follows:

struct flightheader {
ushort flightnumber; // matches what's in the $D record
ulong flags; // matches the "feature flags" in the $C
record
ushort unknown; // may contain flags about what units (e.g. F
vs. C)
ushort interval_secs; // record interval, in seconds (usually 6)
datebits dt; // date as fielded bits, see struct below
timebits tm; // time as fielded bits, see struct below
};

The interval_secs field does not appear always to be the case,
sometimes it is
a value that doesn't make sense. In that case the default interval is 6
secs. But that field may not be fully understood regarding when it is
not the
interval.

The datebits/timebits are bit fields as follows:

// pack a date into 16 bits
struct datebits {
unsigned day:5;
unsigned mon:4;
unsinged year:7;
};

struct timebits {
unsigned secs:5; // #secs / 2 is stored
unsigned mins:6;
unsigned hrs:5;
};


2. Data records

The data is best thought of as an array of 48 short integer values.
Each array
element is initialized to 0xf0. Each stored data record is then just
the
one byte value difference from the previous value (EGTs are an
exception and
have a "scale" byte that can make them a 2 byte short difference).

The difference data is then compressed using a system of simply
excluding that
which does not change. Bit flags indicate which fields are changed. The
fields
are further broken into 6 sets of 8, and entire "sets" can be
suppressed. Again, there are bit flags indicating which sets are
present.

The fields are broken into 6 sets of 8, and both the field and the set
can be suppressed for a given data record. The existence of sets and
fields in
the data record are flagged with bit flags.

The timestamp is not stored and is implied by the initial date/time
from the
flightheader incremented by the interval_secs.

The resultant stream is just the bytes that exist where indicated by a
bit
flag.

The data differences record can be roughly thought of as follows,
though
fields will be compressed away based on bit flags in the flags fields.

struct record {
// These first three bytes always exist
byte decodeflags[2]; // Always identical, don't know why there are
two
byte repeatcount;

// The following are compressed based on bits in decodeflags bytes
byte fieldflags[6]; // init to zero when considering each data rec
byte scaleflags[2];
byte signflags[6];

// The following are compressed based on bits in
fieldflags/scaleflags
byte fielddif[48]; // dif values to apply to the datarec struct
byte scaledif[16];

// checksum of all of this record's bytes (just the ones that exist
// in the file)
byte checksum;
};

In all cases seen, decodeflags[0] == decodeflags[1]. It's possible that
decodeflags[1] was meant to signal existence of the signflags bytes
independently, but that has not been observed.

For each bit set in (decodeflags[0] & (1 x)) (where 0 = x && x
6),
fieldflags[x] and signflags[x] will exist. For each bit set in
(decodeflags[0]
& (0x40 x) (where 0 = x && x 2), scaleflags[x] will exist.

If repeat count is non-zero, output the current data set again
(incrementing
the timestamp) and then continue processing.

For each bit x set in fieldflags (i.e. fieldflags[x/8] & (1 (x%8))
!= 0),
the corresponding fielddif[x] value will exist (otherwise fielddif[x]
remains
zero, i.e. no change from the previous value). If signbits bit x is set
subtract the fielddif[x] value else add the fielddif[x] value to that
field in
the data record.

If the scaleflags bit x is set, then scaledif[x] will be the high order
byte
of the difference value, so add (or subtract, based on the field's
signflags
bit) (scaledif[x] 8) to the xth field.

Each byte of scaleflags only applies to the first 8 (EGT/TIT) values of
the
record for each engine (see record definition below). scaleflags[1]
will be
zero for single engine cases.

The data fields are all 16 bit (short integer) values. Init all values
to 0xf0
for the first record, then apply the difs of each record above. They
are
ordered as follows, noting that some fields only apply to some
configurations
of the EDM monitors based on features provided. In particular, some of
the
EDM-800 features overlap with some of the EDM-760 twin features. In the
single
engine cases the twin fields are ignored. I suspect, but haven't
confirmed,
that for 7/8/9 cyl engines they also use some of the fields beginning
at the
twin data offset. Several fields (BAT, USD, FF, MAP) are stored as an
integer
of the value*10 (i.e. the BAT field stores tenths of volts), so it must
be
adjusted for output.

struct datarec {
// first byte of val/sign/scale flags
short egt[6];
short t1;
short t2;

// second byte of val/sign(/scale?) flags
short cht[6];
short cld;
short oil;

// third byte of val/sign(/scale?) flags
short mark;
short unk_3_1;
short cdt;
short iat;
short bat;
short oat;
short usd;
short ff;

// fourth byte of val/sign(/scale?) flags
short regt[6]; // right engine for EDM-760
union {
short hp; // single engine EDM-800
short rt1; // twin engine EDM-760
};
short rt2;

// fifth byte of val/sign(/scale?) flags
short rcht[6]; // right engine for EDM-760
short rcld;
short roil;

// sixth byte of val/sign(/scale?) flags
short map; // single engine EDM-800
short rpm; // single engine EDM-800
union {
short rpm_highbyte; // single engine EDM-800
short rcdt; // twin engine EDM-760
};
short riat;
short unk_6_4;
short unk_6_5;
short rusd;
short rff;

};

SPECIAL CASES AND EXCEPTIONS:

- DIF is a computed field and must be computed for each record output.

- If HP exists (i.e. EDM-800), HP must be initialized to zero instead
of 0xf0
before the first record.
- If RPM_HIGHBYTE exists, this is the high byte of the RPM difference,
similar to the scale bytes of the EGT fields. The sign of
add/subtract must
follow the signflag from just the RPM field.

Loop over a given flight's data parsing the difference records as above
until
you reach the end of that flight's data. There might be a pad byte at
the end
of the whole flight's data if the last record ends on an odd number of
bytes
(remember flight data length is stored as the number of shorts, not
number of
bytes). The next byte will begin the next flight's data.

************************************************** ********/


#include tchar.h

#include stdio.h
#include stdlib.h
#include stddef.h
#include stdarg.h
#include minmax.h

#include assert.h

#include io.h
#include fcntl.h
#include time.h

#include sys/types.h
#include sys/stat.h





//
// Some general helper definitions and functions
//



typedef unsigned char byte;
typedef unsigned short ushort;
typedef unsigned long ulong;

#define countof(array) (sizeof(array)/sizeof(array[0]))


// Fatal error - give message and exit
static void errexit(const char* msg,...)
{
assert(msg != NULL);
va_list args;
va_start(args,msg);
vprintf(msg,args);
exit(1);
}

// Helpers to swap byte order for big endian
static inline ushort byteswap(ushort w) {
return (((w & 0xFF00) 8) |
((w & 0x00FF) 8));
}

static inline ulong byteswap(ulong dw) {
return (((dw & 0xFF000000) 24) |
((dw & 0x00FF0000) 8) |
((dw & 0x0000FF00) 8) |
((dw & 0x000000FF) 24));
}

// Generic bit vector manipulations
static bool testbit(const void* pv,unsigned bitoffset)
{
assert(pv != NULL);
assert(0 = bitoffset && bitoffset 128); // arbitrary limit for
argument sanity checking
return ((((byte*)pv)[bitoffset/8] & (1 (bitoffset%8))) != 0);
}

static void setbit(void* pv,unsigned bitoffset)
{
assert(pv != NULL);
assert(0 = bitoffset && bitoffset 128); // arbitrary limit for
argument sanity checking
((byte*)pv)[bitoffset/8] |= (byte)(1 (bitoffset%8));
}

static void clearbit(void* pv,unsigned bitoffset)
{
assert(pv != NULL);
assert(0 = bitoffset && bitoffset 128); // arbitrary limit for
argument sanity checking
((byte*)pv)[bitoffset/8] &= ~(byte)(1 (bitoffset%8));
}

// Utility class to save a value and a pointer to it and later restore
the value
template class T class pushpop {
T savedval;
T* savedptr;
pushpop(); // prohibit
pushpop(const pushpop& p); // prohibit
pushpop& operator=(const pushpop& p); // prohibit
public:
pushpop(T* ptr,T val) : savedval(*ptr),savedptr(ptr) {
assert(ptr != NULL);
*ptr = val;
}
~pushpop(void) {
*savedptr = savedval;
}
};



//
// Couple routines for wildcard handling
//

char **getfilelist(const char* fnam)
{
char drive[_MAX_DRIVE]={0};
char dir[_MAX_DIR]={0};
char name[_MAX_FNAME]={0};
char ext[_MAX_EXT]={0};
char path[_MAX_PATH]={0};

_splitpath(fnam,drive,dir,name,ext);

char **retval=NULL;
int n=0,nalloc=0;

struct _finddata_t fdata;
intptr_t hf;
if((hf = _findfirst(fnam,&fdata)) == -1)
errexit("Unable to find file %s",fnam);
do {
if(n = nalloc) {
nalloc = max(8,nalloc*2);
retval = (char**)realloc(retval,sizeof(char*)*nalloc);
}
_splitpath(fdata.name,NULL,NULL,name,ext);
_makepath(path,drive,dir,name,ext);
retval[n] = (char*)malloc(strlen(path)+1);
strcpy(retval[n++],path);
} while(_findnext(hf,&fdata) == 0);
_findclose(hf);
retval[n] = NULL;
return retval;
}

static void freelist(char** list)
{
if(list) {
char** p = list;
while(*p) free(*p++);
free(list);
}
}







//
// Program argument flags
//

static bool s_bDisplayHeaders=false; // -h
static bool s_bDebugDetail=false; // -d
static bool s_bCompareCSV=false; // -c
static bool noflights=false; // -n
static ushort s_nOnlyFlight=0; // -f
static bool s_bSuppressSuffix=false; // -s
static bool s_bRecalcChecksums=false; // -r


//
// File handling - just read the whole darn .DAT file into memory
//

static byte* s_pFileBytes; // =NULL
static size_t s_nAlloc;
static size_t s_nFileBytes;
static char s_szCurrFile[_MAX_PATH];

static void read_file(const char* szFilename)
{
assert(szFilename != NULL && strlen(szFilename));
strcpy(s_szCurrFile,szFilename);
int fd = _open(szFilename,_O_BINARY | _O_RDONLY );
if(fd == -1)
errexit("Unable to open file %s\n%s",szFilename,strerror(errno));
struct _stat filestats;
if(_fstat(fd,&filestats) 0)
errexit("Unable to get file size %s",szFilename);
if(s_nAlloc (size_t)filestats.st_size) {
if(!(s_pFileBytes = (byte*)realloc(s_pFileBytes,filestats.st_size)))
errexit("Memory allocation failed (%d bytes)",filestats.st_size);
s_nAlloc = filestats.st_size;
}
if((s_nFileBytes = _read(fd,s_pFileBytes,filestats.st_size)) = 0)
errexit("Error reading file %s\n%s",szFilename,strerror(errno));
_close(fd);
}

static void setdir(const char* basenam,char* outname,size_t outsize)
{
assert(basenam != NULL && outname != NULL && outsize = _MAX_PATH);
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char name[_MAX_FNAME];
char ext[_MAX_EXT];

_splitpath(s_szCurrFile,drive,dir,NULL,NULL);
_splitpath(basenam,NULL,NULL,name,ext);
_makepath(outname,drive,dir,name,ext);
}

static void write_file(const char* szFilename,const void* bytes,size_t
nbytes)
{
assert(szFilename != NULL && strlen(szFilename));
assert(bytes != NULL && nbytes 0);
int fd = open(szFilename,_O_BINARY | _O_CREAT | _O_WRONLY | _O_TRUNC,
_S_IWRITE);
if(fd == -1)
errexit("Unable to open output file
%s\n%s",szFilename,strerror(errno));
if(_write(fd,bytes,(unsigned)nbytes) != nbytes)
errexit("Error writing file %s\n%s",szFilename,strerror(errno));
_close(fd);
}

static void write_renamed_file(const char* szSuffix)
{
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char name[_MAX_FNAME];
char ext[_MAX_EXT];
char newpath[_MAX_PATH];
_splitpath(s_szCurrFile,drive,dir,name,ext);
strcat(name,szSuffix);
_makepath(newpath,drive,dir,name,ext);
write_file(newpath,s_pFileBytes,s_nFileBytes);
}


//
// Data definitions for various records used
//

#pragma pack(push)
#pragma pack(1)


// $U record
static char tailnum[16]; // should be enough space

// $A record
static struct {
ushort voltshi;
ushort voltslo;
ushort dif;
ushort cht;
ushort cld;
ushort tit;
ushort oilhi;
ushort oillo;
} limits;

// $C record
static struct {
ushort model;
ulong flags; // configuration bit flags
ushort unknown_value; // maybe more bit flags?
ushort firmware_version; // n.nn * 100
} config;

//Decoding of the configuration bit flagss:
//-m-d fpai r2to eeee eeee eccc cccc cc-b
//
// e = egt (up to 9 cyls)
// c = cht (up to 9 cyls)
// d = probably cld
// b = bat
// o = oil
// t = tit1
// 2 = tit2
// a = OAT
// f = fuel flow
// r = CDT (also CARB - apparently it's not distinguished in the CSV
output)
// i = IAT
// m = MAP
// p = RPM
// *** e and c may be swapped
// *** d and b may be swapped (but seem to always occur anyway)
// *** m, p and i may be swapped among themselves

static const int MAX_CYLS=9; // up to 9 cyls possible

static inline unsigned NUMCYLS(ulong flg=config.flags) {
ulong mask = 0x00000004;
unsigned n=0;
while(n MAX_CYLS && (flg & mask)) {
n++;
mask = 1;
}
return n;
}
static inline unsigned NUMENGINE() {
return (config.model == 760)?2:1;
}
static const ulong F_BAT = 0x00000001;
static const ulong F_C1 = 0x00000004;
static const ulong F_C2 = 0x00000008;
static const ulong F_C3 = 0x00000010;
static const ulong F_C4 = 0x00000020;
static const ulong F_C5 = 0x00000040;
static const ulong F_C6 = 0x00000080;
static const ulong F_C7 = 0x00000100;
static const ulong F_C8 = 0x00000200;
static const ulong F_C9 = 0x00000400;
static const ulong F_E1 = 0x00000800;
static const ulong F_E2 = 0x00001000;
static const ulong F_E3 = 0x00002000;
static const ulong F_E4 = 0x00004000;
static const ulong F_E5 = 0x00008000;
static const ulong F_E6 = 0x00010000;
static const ulong F_E7 = 0x00020000;
static const ulong F_E8 = 0x00040000;
static const ulong F_E9 = 0x00080000;
static const ulong F_OIL = 0x00100000;
static const ulong F_T1 = 0x00200000;
static const ulong F_T2 = 0x00400000;
static const ulong F_CDT = 0x00800000; // also CRB
static const ulong F_IAT = 0x01000000;
static const ulong F_OAT = 0x02000000;
static const ulong F_RPM = 0x04000000;
static const ulong F_FF = 0x08000000;
static const ulong F_USD = F_FF; // duplicate
static const ulong F_CLD = 0x10000000; // Uh - I think.
static const ulong F_MAP = 0x40000000;
static const ulong F_DIF = F_E1|F_E2; // DIF exists if there's more
than one EGT
static const ulong F_HP = F_RPM|F_MAP|F_FF;
static const ulong F_MARK= 0x00000001; // 1 bit always seems to exist

// quick way to define a bunch of funcs...
#define HAS(what) static inline bool HAS##what(ulong flg=config.flags)
{return ((flg & F_##what) == F_##what);}
HAS(RPM)
HAS(FF)
HAS(HP)

// $F record
static struct {
ushort warn1;
ushort capacity;
ushort warn2;
ushort kf1;
ushort kf2;
} fuel;

// $T record
static struct {
ushort mon;
ushort day;
ushort yr;
ushort hh;
ushort mm;
ushort unknown_value;
} timestamp;

// $L record
static ushort headerend;

// $D record
struct flight {
ushort flightnum;
ushort data_length; // data length is expressed as # of 16 bit words
};


// First record in each flight's data stream
struct flightheader {
ushort flightnum;
ulong flags;
ushort unknown_value; // Don't know this one yet
ushort interval_secs; // Hmmm... have seen some
counter-examples!?
ushort dt; // see decode_datebits
ushort tm; // see decode_timebits
};


// Each record of data stream
union datarec {
short sarray[1]; // syntactical shorthand
struct {
// first byte of val/sign/scale flags
short egt[6];
short t1;
short t2;

// second byte of val/sign(/scale?) flags
short cht[6];
short cld;
short oil;

// third byte of val/sign(/scale?) flags
short mark;
short unk_3_1;
short cdt;
short iat;
short bat;
short oat;
short usd;
short ff;

// fourth byte of val/sign(/scale?) flags
short regt[6];
union {
short hp; // single engine EDM-800
short rt1; // twin engine EDM-760
};
short rt2;

// fifth byte of val/sign(/scale?) flags
short rcht[6];
short rcld;
short roil;

// sixth byte of val/sign(/scale?) flags
short map; // single engine EDM-800
short rpm; // single engine EDM-800
union {
short rpm_highbyte; // single engine EDM-800
short rcdt; // twin engine EDM-760
};
short riat;
short unk_6_4;
short unk_6_5;
short rusd;
short rff;

short dif[2]; // HACK - this gets computed
byte naflags[6];
};

datarec(void) {
for(int i=0;i sizeof(*this)/sizeof(ushort);i++)
sarray[i] = 0x00f0;
if(NUMENGINE() == 1) {
// seen only one example of this... unclear why it's an exception to
// the 0xf0 initializations.
hp = 0;
rpm_highbyte = 0; // really a "scale" byte
}

dif[0] = dif[1] = 0;
memset(naflags,0,sizeof(naflags));
}

void calcstuff(ulong configflags);

};

static const unsigned TWINJUMP=offsetof(datarec,regt)/sizeof(short); //
offset of 2nd engine egt fields


// DIF is calculated
void datarec::calcstuff(ulong configflags)
{
int nCyls = NUMCYLS(configflags);
// Haven't tested 7/8/9 cyl cases. Note that this will likely
// have to be tweaked a little if the 7/8/9 cyl cases don't
// store their egts all consecutively (as I suspect they don't).
assert(nCyls = 6);
for(unsigned j=0;j NUMENGINE();j++) {
short emax=-1,emin=0x7fff;
for(int i=0;i nCyls;i++) {
int idx = i + j*TWINJUMP;
if(!testbit(naflags,idx)) {
if(egt[idx] emin) emin = egt[idx];
if(egt[idx] emax) emax = egt[idx];
}
}
dif[j] = emax - emin;
}

if(HASRPM(configflags)) {
rpm += (rpm_highbyte 8);
rpm_highbyte = 0; // ??
}
}


// HACK ALERT - special case for the RPM value, which seems to have the
"scale"
// in the next data field, so we need to recognize it and account for
it because
// they don't make the scale's sign bit match. See the special case
lines in
// parse_data() that take care of this.
static const unsigned
RPM_FIELD_NUM=offsetof(datarec,rpm)/sizeof(short);
static const unsigned
RPM_HIGHBYTE_FIELD_NUM=offsetof(datarec,rpm_highby te)/sizeof(short);


//
// This table is a description of the fields in data records, with
offsets and
// text headers and the like. Be sure to KEEP THE FIELDS SORTED IN
ORDER OF
// THE CSV FILE OUTPUT!!!

// a shorthand macro to save typing in the table...
#define FLG(name) #name,F_##name

static struct {
bool bPerEngine; // true if a val is per engine, false if just
one val (EDM-760)
int nOffset; // offset of field in rec.sarray
unsigned nScale; // some are scaled by 10, most are not
const char* szName; // title of field in CSV file
ulong nFeatureFlag;
} const fielddesc[]={
{true , 0, 1,FLG(E1)},
{true , 1, 1,FLG(E2)},
{true , 2, 1,FLG(E3)},
{true , 3, 1,FLG(E4)},
{true , 4, 1,FLG(E5)},
{true , 5, 1,FLG(E6)},
{true , 8, 1,FLG(C1)},
{true , 9, 1,FLG(C2)},
{true ,10, 1,FLG(C3)},
{true ,11, 1,FLG(C4)},
{true ,12, 1,FLG(C5)},
{true ,13, 1,FLG(C6)},
{true , 6, 1,FLG(T1)},
{true , 7, 1,FLG(T2)},
{true ,15, 1,FLG(OIL)},
{true ,-1, 1,FLG(DIF)},
{true ,14, 1,FLG(CLD)},
{false,21, 1,FLG(OAT)},
{true ,18, 1,FLG(CDT)}, // not sure whether these are available in
the twin model
{true ,19, 1,FLG(IAT)},
{false,20,10,FLG(BAT)},
{true ,23,10,FLG(FF)},
{true ,22,10,FLG(USD)},
{false,41, 1,FLG(RPM)}, // these only are available in the single
EDM models
{false,40,10,FLG(MAP)},
{false,30, 1,FLG(HP)},

{false,16, 1,FLG(MARK)}
};

#pragma pack(pop)

static flight flightlist[512]; // hopefully enough capacity for any
single .DAT file
static unsigned s_nFlights;

static byte* s_pHeaderEnd; // point to end of headers for later
processing



// initialize various module variables
static void reset_vars()
{
tailnum[0] = 0;
memset(&limits,0,sizeof(limits));
memset(&config,0,sizeof(config));
memset(&fuel,0,sizeof(fuel));
memset(&timestamp,0,sizeof(timestamp));
headerend = 0;
memset(flightlist,0,sizeof(flightlist));
s_nFlights = 0;
s_pHeaderEnd = NULL;
}


//
// Compute the checksum on the textual file header records
//
static void test_header_checksum(const char* line)
{
assert(line);
const char* endp = strrchr(line,'*');
const char* p = line+1;

if(!endp || *endp != '*')
errexit("Header checksum format error: %s\n",line);

ushort testval;
byte cs=0;
if(sscanf(endp+1,"%hx",&testval) != 1)
errexit("Header checksum format error: %s\n",line);
while(p endp)
cs ^= *p++;
if(testval != cs)
errexit("Header checksum failed: %s",line);
}

//
// Helper to parse a list of short values, which is most
// of the text header records.
//
static void parseshorts(void* results,const void* line_in,unsigned
count)
{
const char* line = reinterpret_castconst char*(line_in);
assert(results != NULL && count 0);
assert(line != NULL && *line == '$'); // start from beginning of
record
ushort* sresults = static_castushort*(results);
line = strchr(line,','); // skip $X header
for(unsigned i=0;i count;i++) {
if(!sscanf(line,",%hu",sresults+i))
errexit("Not enough values (%d): %s\n",count,line_in);
line = strchr(line+1,','); // this will be null on last iteration
}
}


// parse out the relevent bits of the header
static void parse_headers(void)
{
byte* lf;
byte* line;

for(line=s_pFileBytes;line s_pFileBytes+s_nFileBytes && (lf =
(byte*)memchr(line,'\r',s_nFileBytes));line = lf) {
assert(*lf == '\r' && *(lf+1) == '\n');
pushpopbyte savecr(lf,0); // terminate line temporarily

lf += 2; // point to next record

// check the checksum
test_header_checksum((const char*)line);

// maybe print the line
if(s_bDisplayHeaders)
printf("%s\n",line);

// parse the pieces into separate strings, comma delimited (and strip
the trailing *xx "checksum")
if(*line != '$')
errexit("Expected $ at beginning of record:\n -- %s",line);

switch(*(line+1)) {
case 'U': // tail number
line += 3; // skip past comma
for(int i=0;i sizeof(tailnum)-1 && *line && *line != '*';i++)
tailnum[i] = *line++;
tailnum[i] = 0;
break;
case 'A': // limits info
parseshorts(&limits,line,8);
break;
case 'F': // fuel flow config info
parseshorts(&fuel,line,5);
break;
case 'T': // timestamp info
parseshorts(&timestamp,line,6);
break;
case 'C': // instrument configuration info
parseshorts(&config,line,5);

// HACK - change the version now while we're pointing at it
// in case we write this line back out using the -r program
// option.
if(config.firmware_version = 300) {
char* p = (char*)(line+1);
char* ver = strrchr(p,',');
char* endp = strrchr(p,'*');
if(!ver++ || !endp) // basic error check
break;
while(*ver == ' ') ver++; // skip spaces
if(endp-ver != 3) // basic error check
break;

*ver++ = '2';
*ver++ = '9';
*ver++ = '9';
byte cs=0;
while(p endp)
cs ^= *p++;
char buf[8];
sprintf(buf,"%02X",cs);
endp++;
*endp++ = buf[0];
*endp++ = buf[1];
}
break;
case 'L': // end of headers, unknown meaning
parseshorts(&headerend,line,1);

// Save end of headers for other funcs
s_pHeaderEnd = lf;

// NOTE: Normal exit path of function!
return;

case 'D': // flight info
if(s_nFlights = countof(flightlist))
errexit("This program can only handle %d flights per
file",countof(flightlist));
parseshorts(&flightlist[s_nFlights++],line,2);
break;
default:
printf("Unrecognized header record:\n -- %s\n",line);
break;
}
}

// Note that this is not the typical exit path of the function, we
SHOULD
// find the 'L' record and return from there. This is the error exit
path.
errexit("Unexpected end of .DAT file");
}

static void decode_datebits(ushort dt,ushort *m,ushort *d,ushort *y)
{
assert(m && d && y);
// Date is coded into 16 bits as follows
// struct {
// ushort day:5;
// ushort mon:4;
// ushort yr:7;
// };

*d = (dt & 0x001f);
*m = (dt & 0x01e0) 5;
*y = (dt & 0xfe00) 9;
};

static void decode_timebits(ushort tm,ushort *h,ushort *m,ushort *s)
{
assert(h && m && s);
// Time is coded into 16 bits as follows
// struct {
// ushort secs:5; // #secs / 2 is stored
// ushort mins:6;
// ushort hrs:5;
// };

*s = (tm & 0x001f) * 2;
*m = (tm & 0x07e0) 5;
*h = (tm & 0xf800) 11;
};


// time helpers
static void cvttime(time_t t,ushort& hh,ushort &mm,ushort& ss)
{
struct tm* tmptr = localtime(&t);
hh = tmptr-tm_hour;
mm = tmptr-tm_min;
ss = tmptr-tm_sec;
}

static time_t inittime(ushort m,ushort d,ushort y,ushort hh,ushort
mm,ushort ss)
{
assert(1 = m && m = 12); // input not zero based
struct tm tmp;
// tmp.tm_wday and tmp.tm_yday are ignored in mktime
tmp.tm_mon = m-1;
tmp.tm_mday = d;
tmp.tm_year = (y 50)?y+100:y; // note that century issue will be
a problem after 2050
tmp.tm_sec = ss;
tmp.tm_min = mm;
tmp.tm_hour = hh;
tmp.tm_isdst = -1;
return mktime(&tmp);
}

// an alternate form
static time_t inittime(ushort dtbits,ushort tmbits)
{
ushort m,d,y,hh,mm,ss;
decode_datebits(dtbits,&m,&d,&y);
decode_timebits(tmbits,&hh,&mm,&ss);
return inittime(m,d,y,hh,mm,ss);
}


//
// Format the data record into the format of the .CSV output
//
static void formatdata(time_t t,const datarec& rec,char* outbuf,size_t
outsize)
{
assert(outbuf != NULL && outsize 0);

// start printing the CSV line
size_t nout = 0;
ushort hh,mm,ss;

cvttime(t,hh,mm,ss);
nout += sprintf(outbuf+nout,"\"%d:%d:%d\"",hh,mm,ss);

for(unsigned j=0;j NUMENGINE();j++) {

// loop through each field except "MARK" (the last field)
for(unsigned i=0;i countof(fielddesc)-1;i++) {
if(!fielddesc[i].bPerEngine && j NUMENGINE()-1)
continue;
// making the & logic equal the flags allows some of the combined
flags to work (e.g. HP)
if((fielddesc[i].nFeatureFlag & config.flags) ==
fielddesc[i].nFeatureFlag) {
int offset = fielddesc[i].nOffset;
// yet another special case hack to cover the computed DIF field
if(offset 0)
nout += sprintf(outbuf+nout,",%d",rec.dif[j]);
else {
if(fielddesc[i].bPerEngine)
offset += j*TWINJUMP;
if(!testbit(rec.naflags,offset)) {
short s = rec.sarray[offset];
nout += sprintf(outbuf+nout,",%d",s/fielddesc[i].nScale);
if(s%fielddesc[i].nScale)
nout += sprintf(outbuf+nout,".%d",s%fielddesc[i].nScale);
}
else
nout += sprintf(outbuf+nout,",\"NA\"");
}
}
}
}

// "MARK" field special case since it's output as a string not a
numeric value
nout += sprintf(outbuf+nout,",%s\n",rec.mark?"\"S\"":"");

assert(nout outsize);
}


//
// This will handle file output to the resultant CSV file.
// It also contains the "compare CSV" logic, which
// is mostly for testing and development - open
// the "known" .CSV file and compare each line of our
// output to the "known" file. Handy to compare lots
// of data to track down those last unrecognized bits.
//

static FILE* s_fCompareCSV;
static FILE* s_fOutputCSV;

static void opencsv(ushort flightnum)
{
char fnam[_MAX_FNAME];
char path[_MAX_PATH];

sprintf(fnam,"F%05d%s.CSV",flightnum,(s_bSuppressS uffix)?"":"-HACK");
setdir(fnam,path,sizeof(path));
if(!(s_fOutputCSV=fopen(path,"w")))
errexit("Unable to open output file %s:\n%s",path,strerror(errno));

if(!s_bCompareCSV)
return;

sprintf(fnam,"F%05d.CSV",flightnum);
setdir(fnam,path,sizeof(path));
if(!(s_fCompareCSV = fopen(path,"r")))
errexit("Unable to open comparison file
%s:\n%s",path,strerror(errno));
}

static void closecsv(void)
{
if(s_fOutputCSV) {
fclose(s_fOutputCSV);
s_fOutputCSV = NULL;
}
if(s_fCompareCSV) {
fclose(s_fCompareCSV);
s_fCompareCSV = NULL;
}
}

static void outputline(const char* line,bool bsuppressdiff=false)
{
assert(line != NULL);

if(s_fOutputCSV)
if(fputs(line,s_fOutputCSV) == EOF)
errexit("Error writing output file.\n%s",strerror(errno));

const char* prefix="";
if(s_bCompareCSV) {
if(!s_fCompareCSV)
errexit("Comparison file not opened correctly (??)");

char buf[512]={0};
if(!fgets(buf,sizeof(buf),s_fCompareCSV))
errexit("Unexpected end of comparison file");

if(strcmp(buf,line) && !bsuppressdiff) {
prefix = "!";
printf("%s%s",prefix,buf);
printf("%s%s",prefix,line);
return;
}
}

if(s_bDebugDetail)
printf("%s%s",prefix,line);
}

// Minor hack - we go through and write all the data before we know how
many hours
// to put in the "Duration" line of the CSV file, so we just save where
we were
// in that file and then come back to it.
static long s_DurationOffset;

static void outputheaders(const flightheader& fhead)
{
time_t t = time(NULL);
struct tm* tp = localtime(&t);
char outbuf[512];
int nout;

// Note the output will indicate JPIHack instead of EZSave, but
// that's the only difference between the CSV file output versus
// what EZSave produces.
sprintf(outbuf,"\"JPIHack
%02d/%02d/%02d\"\n",tp-tm_mon+1,tp-tm_mday,tp-tm_year%100);
outputline(outbuf,true); // ignore diffs in this line - they won't
ever match
sprintf(outbuf,"\"EDM-%4d V %3d J.P.Instruments (C)
1998\"\n",config.model,config.firmware_version);
outputline(outbuf);
sprintf(outbuf,"\"Aircraft Number %s\"\n",tailnum);
outputline(outbuf);

ushort y,m,d;
ushort hh,mm,ss;
decode_datebits(fhead.dt,&m,&d,&y);
decode_timebits(fhead.tm,&hh,&mm,&ss);
sprintf(outbuf,"\"Flight #%d %d/%d/%d
%d:%d:%d\"\n",fhead.flightnum,m,d,y,hh,mm,ss);
outputline(outbuf);

// UNKNOWN: There's probably a bit somewhere that the engine data is C
vs F, but I haven't seen it
const char* OAT;
if(fhead.unknown_value & 0x20) // not 100% sure if this is the right
bit, but it appears to be
OAT = "F";
else
OAT = "C";
nout = sprintf(outbuf,"\"Eng Deg F OAT Deg %s ",OAT);
if(HASFF(fhead.flags))
nout += sprintf(outbuf+nout," F/F GPH"); // UNKNOWN: some bit
somewhere probably indicates other units for FF
nout += sprintf(outbuf+nout,"\"\n");
outputline(outbuf);

// See note above about fixing the Duration later.
s_DurationOffset = ftell(s_fOutputCSV); // update duration after all
data is read
sprintf(outbuf,"\"Duration 0.00Hours Interval %d seconds
\"\n",fhead.interval_secs);
outputline(outbuf,true); // ignore diffs in this line - they won't
match 'til later

// write the CSV field titles
nout = sprintf(outbuf,"\"TIME\"");
for(unsigned j=0;j NUMENGINE();j++) {
for(unsigned i=0;i countof(fielddesc);i++) {
const char* eng;
if(!fielddesc[i].bPerEngine || NUMENGINE() == 1)
eng = "";
else if(j 0)
eng = "R";
else
eng = "L";
if((fielddesc[i].nFeatureFlag & fhead.flags) ==
fielddesc[i].nFeatureFlag &&
(fielddesc[i].bPerEngine || j == NUMENGINE()-1))
nout += sprintf(outbuf+nout,",\"%s%s\"",eng,fielddesc[i].szName);
}
}
nout += sprintf(outbuf+nout,",\n"); // EZSave appended an extra comma
in the field names line...
outputline(outbuf);
}

static const float SECS_PER_HOUR=(float)60.0 * (float)60.0;
static void write_duration(time_t t,const flightheader& fhead)
{
if(!s_fOutputCSV)
return;

time_t start = inittime(fhead.dt,fhead.tm);
fseek(s_fOutputCSV,s_DurationOffset,SEEK_SET);
fprintf(s_fOutputCSV,"\"Duration
%5.2f",((float)(t-start))/SECS_PER_HOUR);
}


// This routine is just for dumping bits/bytes if you're scratching
your head
// over the contents of the .DAT file.
static void dumpflightheader(const flightheader& fhead)
{
ushort y,m,d;
ushort hh,mm,ss;
decode_datebits(fhead.dt,&m,&d,&y);
decode_timebits(fhead.tm,&hh,&mm,&ss);
printf("FltHdr: #%5u, flgs 0x%08x, unk %02x, secs %d, %2d/%02d/%02d
%2d:%02d:%02d\n",fhead.flightnum,fhead.flags,fhead .unknown_value,fhead.interval_secs,m,d,y,hh,mm,ss) ;
}



//
// Checksum the various data records
//
// NOTE: This is the only change between the "new" .DAT files
// and the "old" .DAT files, i.e. they changed the XOR based
// checksum to the SUM based checksum in firmware versions after 3.00.
//

static byte calc_new_checksum(const void* pBytes,size_t nbytes)
{
assert(pBytes && nbytes 0);
byte cksum=0;
const byte* p = reinterpret_castconst byte*(pBytes);
while(nbytes-- 0)
cksum += *p++;
cksum = -cksum;
return cksum;
}

static byte calc_old_checksum(const void* pBytes,size_t nbytes)
{
assert(pBytes && nbytes 0);
byte cksum=0;
const byte* p = reinterpret_castconst byte*(pBytes);
while(nbytes-- 0)
cksum ^= *p++;
return cksum;
}

static byte calc_data_checksum(const void* pBytes,size_t nbytes)
{
if(config.firmware_version = 300)
return calc_new_checksum(pBytes,nbytes);
else
return calc_old_checksum(pBytes,nbytes);
}



//
// The main function for iterating through each flight and parsing out
the data
//

static void parse_data(void)
{
assert(s_pHeaderEnd != NULL);
byte* pFlight;
byte* pEnd;
byte* pTop = s_pHeaderEnd;
unsigned i;
char outbuf[512]; // should be ample

//
// Iterate through every flight's data
//
for(unsigned iFlight=0;iFlight s_nFlights;pTop=pEnd,iFlight++) {

// Point at the data for this flight (and it's end), and sanity check
the length
pFlight = pTop;
pEnd = pFlight + flightlist[iFlight].data_length * sizeof(ushort);
if(pEnd = s_pFileBytes+s_nFileBytes)
errexit("Data ends unexpectedly");
if(pEnd-pFlight sizeof(flightheader))
errexit("Flight %u data length too
short",flightlist[iFlight].flightnum);

// Skip this flight if it's one we're not interested in
if(s_nOnlyFlight && flightlist[iFlight].flightnum != s_nOnlyFlight)
continue;

// Note that ctor will init datarec appropriately
datarec rec;
time_t t;
flightheader fhead;

// Parse the flight header
ushort *usarray = reinterpret_castushort*(&fhead);
for(i = 0;i sizeof(flightheader)/sizeof(ushort);i++) {
usarray[i] = byteswap(*(ushort*)pFlight);
pFlight += sizeof(ushort);
}
if(calc_data_checksum(&fhead,sizeof(flightheader)) != *pFlight++)
errexit("Flight header checksum failed");

// Sanity check the flight
if(fhead.flightnum != flightlist[iFlight].flightnum)
errexit("Flight numbers don't match (%d header, %d data), invalid
file",fhead.flightnum,flightlist[iFlight].flightnum);

// If we care, dump some bit gunk to the screen
if(s_bDebugDetail)
dumpflightheader(fhead);

// Get the time...
t = inittime(fhead.dt,fhead.tm);

// HACK ALERT UNTIL WE FIGURE OUT WHY THE SECONDS IS SOMETIMES ALL
OUT OF WHACK!!
// There's probably a bit field somewhere that controls this (perhaps
one of the
// bits in fhead.unknown_value?), but don't know which one yet. Given
a few
// examples it's probably not too hard to track down.
if(fhead.interval_secs 2 || 512 fhead.interval_secs)
fhead.interval_secs = 6;

// Open the output file
opencsv(fhead.flightnum);

// Output the CSV headers
outputheaders(fhead);

// save this for various loops - syntactical shorthand
unsigned nCyl = NUMCYLS(fhead.flags);


//
// Loop across each data record
//


// Will always read at least 3 bytes, and this ensures we don't go
past
// the end in the event that the data record ends on an odd byte
count.
// (Recall the length spec'd in the headers is given as # of 2 byte
words.)
while((pFlight+3) pEnd) {

// save top of record for later checksumming
byte* pDataRec = pFlight;

// Get the first flags that flag which "sets" of data are there
byte decodeflags[2];
byte repeatcount;
decodeflags[0] = *pFlight++;
decodeflags[1] = *pFlight++;

// Get the repeat count
repeatcount = *pFlight++;
if(s_bDebugDetail) // dump debugging junk if we care
printf("decode %02x %02x repeat
%02x\n",decodeflags[0],decodeflags[1],repeatcount);
assert(decodeflags[0] == decodeflags[1]); // draw attention to
something not seen before

// The repeat count, if present, indicates we should just spit out
the
// previous data that many times (incrementing the timestamp
appropriately).
while(repeatcount--) {
formatdata(t,rec,outbuf,sizeof(outbuf));
outputline(outbuf);
t += fhead.interval_secs;
}

// More debug output handy if we are puzzling out the data file
format
if(s_bDebugDetail) {
printf("sign/scale bytes:");
byte* pTmp = pFlight;
for(i=0;i 8;i++) {
if(decodeflags[0] & (1 i))
printf(" %02x",*pTmp++);
else
printf(" ");
}
// Why only 6?? 'cause otherwise we duplicate the scale bits, I
guess, and they don't ever do that.
// Unclear on why there are two decodeflags - they always seem to
be equal.
// I've never seen scale flags for CHT or other value sets, just
EGT values.
for(i=0;i 6;i++) {
if(decodeflags[1] & (1 i))
printf(" %02x",*pTmp++);
else
printf(" ");
}
printf("\n");
}

// Bit flags that indicate the existence of a given field
// in the compressed stream of difference values.
byte valflags[6];
byte scaleflags[2]; // flags presence of the EGT scale values
byte signflags[6]; // indicates sign of dif value

memset(valflags,0,sizeof(valflags));
memset(scaleflags,0,sizeof(scaleflags));
memset(signflags,0,sizeof(signflags));

// The presence of one of the bits of decodeflags indicates that
// at least one of the group of eight fields of a "set" is present
// and that set's flags will be present.
for(i=0;i countof(valflags);i++)
if(decodeflags[0] & (1 i))
valflags[i] = *pFlight++;

// Check existence of the EGT scale value sets
for(i=0;i countof(scaleflags);i++)
if(decodeflags[0] & (0x40 i))
scaleflags[i] = *pFlight++;
// never seen otherwise - draw attention to new case
assert(scaleflags[1] == 0 || NUMENGINE() 1);

// Get the sign bits
for(i=0;i countof(signflags);i++)
if(decodeflags[1] & (1 i))
signflags[i] = *pFlight++;

// Values are stored as 8 bit difference from previous value (except
EGTs
// which could be a 16 bit difference from previous value). Sign
// bit determines whether the difference value is added or
subtracted.
// For the EGT/TIT fields, scale bit determines whether the high
// order byte of a two byte value is stored.
//
// Note that a difference flagged to exist but equal to zero is
// the flag for "NA". This logic is not perfectly implemented below
// but is hacked in to work for the most part. A little more effort
// could refine the overall elegance a little.

for(i=0;i sizeof(valflags)*8;i++) {
if(testbit(valflags,i)) {
if(*pFlight == 0)
setbit(rec.naflags,i);
else
clearbit(rec.naflags,i);
if(testbit(signflags,i))
rec.sarray[i] -= *pFlight++;
else
rec.sarray[i] += *pFlight++;
}
}

for(unsigned j=0;j sizeof(scaleflags);j++) {
for(i=0;i 8;i++) {
if(testbit(scaleflags+j,i)) {
unsigned idx = j*TWINJUMP + i;
ushort x = *pFlight++;
if(x != 0) {
clearbit(rec.naflags,idx);
x = 8;
if(testbit(signflags,idx))
rec.sarray[idx] -= x;
else
rec.sarray[idx] += x;
}
// else... note that the low byte of the dif value
// would have set the naflags bit already if the
// high byte and low byte were both zero
}
}
}

// HACK ALERT - special case the RPM high byte since it follows
// the sign of the RPM field and doesn't appear to follow its
// own sign bit.
if(NUMENGINE() == 1) {
if(testbit(signflags,RPM_FIELD_NUM)) {
assert(!testbit(signflags,RPM_HIGHBYTE_FIELD_NUM)) ;
rec.rpm_highbyte = -rec.rpm_highbyte;
}
if(rec.rpm_highbyte != 0)
clearbit(rec.naflags,RPM_FIELD_NUM);
}

// Compute the DIF field
rec.calcstuff(fhead.flags);

if(pFlight = pEnd)
errexit("Unexpected end of data record");
if(calc_data_checksum(pDataRec,pFlight-pDataRec) != *pFlight++) {

// DEBUGGING JUNK - dump the bytes of records which don't checksum
correctly
// so we can scrutinize them a bit.
if(s_bDebugDetail) {
int nprint=0;
while(pDataRec pFlight) {
if(!(nprint%16))
printf("\n%08X:",nprint);
if(!(nprint%2))
printf(" ");
printf("%02x",*pDataRec++);
nprint++;
}
printf("\n");
}


errexit("Data checksum failed");

}

// Output the CSV line
formatdata(t,rec,outbuf,sizeof(outbuf));
outputline(outbuf);
t += fhead.interval_secs;

} // END WHILE() (the data record loop)

// Go back and fix the text in the CSV headers
write_duration(t-fhead.interval_secs /* subtract the last
iteration*/,fhead);

if(s_bDebugDetail)
printf("\n");

// End of flight data, close the CSV file
closecsv();

}
}


//
// This corresponds to the -r flag, which will change the .DAT file to
// use the older checksum scheme and allow EZSave to work as it used
to.
//
static void recompute_checksums(void)
{
assert(s_pHeaderEnd != NULL);
byte* pFlight;
byte* pEnd;
byte* pRec;
byte* pTop = s_pHeaderEnd;
unsigned i;

if(config.firmware_version 300) {
printf("This data file is the older version and doesn't need to be
changed\n");
return;
}

//
// Iterate through every flight's data
//
for(unsigned iFlight=0;iFlight s_nFlights;pTop=pEnd,iFlight++) {

// Point at the data for this flight (and it's end), and sanity check
the length
pFlight = pTop;
pEnd = pFlight + flightlist[iFlight].data_length * sizeof(ushort);
if(pEnd = s_pFileBytes+s_nFileBytes)
errexit("Data ends unexpectedly");
if(pEnd-pFlight sizeof(flightheader))
errexit("Flight %u data length too
short",flightlist[iFlight].flightnum);

pRec = pFlight;
pFlight += sizeof(flightheader);

// Rechecksum the flight header
if(calc_data_checksum(pRec,pFlight-pRec) != *pFlight)
errexit("Flight header checksum failed");

*pFlight++ = calc_old_checksum(pRec,pFlight-pRec);


//
// Loop across each data record
//


// Will always read at least 3 bytes, and this accounts for the
possibility
// that the data record ends on an odd byte count even though the
length
// spec'd in the headers is given as # of words.
while((pFlight+3) pEnd) {

pRec = pFlight;

byte decodeflags[2];

decodeflags[0] = *pFlight++;
decodeflags[1] = *pFlight++;
pFlight++; // repeatcount


byte valflags[6];
byte scaleflags[2];

memset(valflags,0,sizeof(valflags));
memset(scaleflags,0,sizeof(scaleflags));

for(i=0;i countof(valflags);i++)
if(decodeflags[0] & (1 i))
valflags[i] = *pFlight++;

// Seems to be the only egt scale flags ever present (??)
for(i=0;i countof(scaleflags);i++)
if(decodeflags[0] & (0x40 i))
scaleflags[i] = *pFlight++;

// never seen otherwise - draw attention to new case
assert(scaleflags[1] == 0 || NUMENGINE() 1);

// These are the sign flags
for(i=0;i countof(valflags);i++)
if(decodeflags[1] & (1 i))
pFlight++;


// Now just loop through the various flags, skip that many bytes,
// and recalc the checksum when we're done.
for(i=0;i sizeof(valflags)*8;i++)
if(testbit(valflags,i))
pFlight++;

for(i=0;i sizeof(scaleflags)*8;i++)
if(testbit(scaleflags,i))
pFlight++;

if(pFlight = pEnd)
errexit("Unexpected end of data record");

if(calc_data_checksum(pRec,pFlight-pRec) != *pFlight)
errexit("Data checksum failed");

*pFlight++ = calc_old_checksum(pRec,pFlight-pRec);

} // END WHILE()


}

write_renamed_file("-HACK");

}

static void usage(void)
{
printf("JPIHACK [-r] [-s] [-c] [-f#] [-h] [-d] [-n] datfiles\n"
"\n"
" datfiles are a list of .DAT files to translate, wildcards
allowed.\n"
"\n"
" -r Instead of translating the .DAT file to .CSV files, this
will\n"
" merely change the .DAT file back to the older format
which is\n"
" supported by EZSave. This should work on files which may
have\n"
" options that are not yet recognized by this program,
such as\n"
" engines with more than 6 cylinders. The resulting .DAT
file\n"
" will be named with the suffix -HACK (e.g.
Ryymmdd-HACK.DAT).\n"
"\n"
" -s Suppress CSV file name suffixing (i.e. no
Fnnnnn-HACK.CSV naming)\n"
" -c Compare to existing CSV files and show diffs\n"
" -f# Display only flight #'s data (# is numeric value)\n"
" -h Display RAW DAT file header records\n"
" -d Display detailed debugging junk\n"
" -n Skip flight data (useful for debugging headers)\n"
"\n"
"Note that most of these options are useful for debugging the .DAT
file\n"
"format and aren't tremendously useful for everyday use.\n"
"\n"
"A few of the less common features of the .DAT files are not fully
understood,\n"
"so if there is some kind of translation error it could be an
option\n"
"that hasn't been seen and debugged yet. Particular cases may
include recording\n"
"engine temps in deg C, fuel flow in other than GPH, and engines
with more than\n"
"six cylinders. Note that you can still use the -r option to get
EZSave to work\n"
"with your file in those cases.\n"
);
exit(0);
}


int main(int argc, char* argv[])
{
int i;

if(argc 2)
usage();

for(i=1;i argc;i++) {
// Note that switches only apply to files that follow them on the cmd
line
if(argv[i][0] == '-' || argv[i][0] == '/') {
switch(tolower(argv[i][1])) {
case '?': usage(); break;
case 'h': s_bDisplayHeaders=true; break;
case 'd': s_bDebugDetail = true; break;
case 'c': s_bCompareCSV = true; break;
case 'n': noflights = true; break;
case 's': s_bSuppressSuffix = true; break;
case 'r': s_bRecalcChecksums = true; break;
case 'f':
if(argv[i][2])
s_nOnlyFlight = atoi(argv[i]+2);
else
errexit("-f argument must have the flight# follow without space
separating it.");
break;
default: errexit("Unknown switch %s\n",argv[i]);
}
}
else {
// wildcards work too
char** filelist = getfilelist(argv[i]);
for(int j=0;filelist[j];j++) {
reset_vars();
printf("%s\n",filelist[j]);
read_file(filelist[j]);
parse_headers();
if(s_bRecalcChecksums)
recompute_checksums();
else if(!noflights) {
if(NUMCYLS() 6)
errexit("This program has never been tested against engines with
more\n"
"than 6 cylinders and almost certainly won't work. It is
likely it can\n"
"be easily adapted with a data sample to work with if you
are so inclined\n"
"and have the source code with which this was released.\n"
"\n"
"However, until then use the -r option to recompute the
data checksums to\n"
"the style of the old format, and then run EZSave on the
resulting .DAT file.\n");
parse_data();
}
}
freelist(filelist);
}
}

return 0;
}

  #3  
Old June 12th 05, 01:39 AM
Frank Ch. Eigler
external usenet poster
 
Posts: n/a
Default


"Josh" writes:

See http://www.geocities.com/joshhurtel/


Nice job. Serves them right.

- FChE
  #4  
Old June 13th 05, 03:31 AM
soxinbox
external usenet poster
 
Posts: n/a
Default

Let us know if they sick their lawyers on you. It would be par for the
coarse.

"Josh" wrote in message
oups.com...
See http://www.geocities.com/joshhurtel/



  #5  
Old June 13th 05, 04:20 AM
Morgans
external usenet poster
 
Posts: n/a
Default


"soxinbox" wrote in message news:je6re.18733

Let us know if they sick their lawyers on you. It would be par for the
coarse.


Right. I wouldn't buy a shuttlecock from them, after their last rampage on
the little guy. Sounds like they are adopting Lowrance's tactics to their
other tricks.
--
Jim in NC


 




Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
JPI Data Format and Conversion Josh Owning 4 June 13th 05 04:20 AM
Turnpoint conversion SW? For Example John Smith Soaring 11 March 23rd 05 06:56 PM
Digital Photos (was: Reno Suite...) RST Engineering Piloting 19 December 13th 04 07:46 PM
Airspace.txt in Soar-High's IGCtoPOV conversion??? Jerome Conners Soaring 0 December 6th 03 11:13 PM


All times are GMT +1. The time now is 03:36 AM.


Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2023, Jelsoft Enterprises Ltd.
Copyright 2004-2023 AviationBanter.
The comments are property of their posters.