/* --------------------------------------------------------------------
 * rgm3800-client.c: configuration and download program for the
 * Royaltec RGM-3800 GPS Tracker
 *
 * $Id: rgm3800-client.c,v 1.25 2009/03/07 00:24:08 akurz Exp $
 *
 * Copyright 2008 by Alexander Kurz
 *
 * This is free software.
 * You may copy and redistibute this software according to the
 * GNU public Licence 3 or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * ----------------------------------------------------------------- */

/* ---------- specify your compiler/platform here */
/* getopt_long from getopt.h */
#define HAVE_GETOPT_H

/* ------------ libs -------------- */
#include<sys/types.h>	/* stat */
#include<sys/stat.h>	/* stat */
#include<sys/time.h>	/* timeval */
#include<termios.h>	/* serial line stuff */
#include<fcntl.h>	/* O_NONBLOCK flags */
#include<unistd.h>	/* read() */
#include<netinet/in.h>	/* ntohl */

#include<stdio.h>
#include<errno.h>	/* error handling */
#include<string.h>
#include<stdlib.h>	/* old getopt ? */
#if defined(HAVE_GETOPT_H)
#include<getopt.h>	/* improved command-line functionality */
#endif

/* --------- global ------------------------------------ */
#define MAX_STRLEN	4096
#define MAX_BLOCKCOUNT	1024
/*#define _POSIX_SOURCE 1	*/	/* POSIX compliant source */
struct termios oldtio_l;
const char version[]	= "$Id: rgm3800-client.c,v 1.25 2009/03/07 00:24:08 akurz Exp $";
const int rgm3800_blocksize[]	= {12,16,20,24,60,0};
int	print_raw_f;
int	timeout;

/* --------- some application specific container ------- */
/* --- device response data */
typedef struct {
  unsigned int blocks;
  char *blockp[MAX_BLOCKCOUNT];
  size_t size[MAX_BLOCKCOUNT];
  unsigned int xor_valid[MAX_BLOCKCOUNT];
} rgm3800_response_t;

/* --- device status and configuration */
typedef struct {
  unsigned int rectype;
  unsigned int unknown1;
  unsigned int unknown2;
  unsigned int overwrite;
  unsigned int unknown4;
  unsigned int interval;
  unsigned int receive;
  unsigned int files;
  unsigned int blocks;
} rgm3800_status_t;

/* --- a directory entry */
typedef struct {
  unsigned int id;
  long int date;
  unsigned int type;
  unsigned int size;
  long unsigned int offset;
} rgm3800_dir_entry_t;

/* --- a raw geo data line */
typedef struct {
  unsigned int type;
  unsigned int time_h;
  unsigned int time_m;
  unsigned int time_s;
  long int date;
  float lat;
  float lon;
  float alt;
  float vel;
  float direction;
  unsigned int dist;
  unsigned short satid[12];
  unsigned short satsnr[12];
} rgm3800_geo_entry_t;

/* --------- print usage-information ------------------- */
void usage(const char *prog){
  fprintf(stderr,
	"Usage: %s [option(s)] [action(s)]\n"
	"  deraults: port=/dev/ttyUSB0 and baudrate=115200n81, write to stdout\n\n"
	"Options:\n"
	"  -b,  --baudrate <number>  default 115200\n"
	"  -t,  --timeout <ms>       timeout after <milliseconds>, default=1500\n"
	"  -p,  --port               serial port e.g. /dev/ttyUSB0, /dev/ttyS0, ...\n"
/*	"  -o,  --output <file>      write to <file> otherwise write to stdout\n" */
	"  -r,  --raw                print raw track data as received from device\n"
	"  -h,  --help               print this help-information and exit\n"
	"  -v,  --version            print version and exit\n\n"
	"Actions:\n"
	"  date                      print current GPS time/date (UTC)\n"
	"  dump                      download all files at once\n"
	"  erase                     erase all data\n"
	"  fwversion                 print firmware version info\n"
	"  get <int>                 download file <number>\n"
	"  ls                        list files\n"
	"  set [<set-param>,...]     set device parameters [<set-param>,...]\n"
	"  status                    print device status\n\n"
	"Set-Params:\n"
	"  i=<int>                   set tracking interval to <seconds>\n"
	"  f=<char>                  set behaviour if mem is full: o=overwrite, s=stop\n"
	"  t=<char>                  set tracked data: 0=minimal(12B/smple),\n"
	"                              1=0+altitude(16B/sample), 2=1+velocity(20B/sample)\n"
	"                              3=2+distance(24B/sample), 4=3+status(60B/sample)\n"
/*
	"  count <int>               enable realtime GPS aquisition for n seconds, 0=infinite\n\n"
*/
	,prog);
}

/* --------- helper ------------------------------------ */
/* swap the byte order
   Network byte order is usually big endian while the RGM3600-byte order
   is little endian. Swapping it allows the use of ntohl() and similar.
*/
uint32_t bswap32(uint32_t in) {
  uint32_t out;
  *((uint8_t*)&out) = *((uint8_t*)&in+3);
  *((uint8_t*)&out+1) = *((uint8_t*)&in+2);
  *((uint8_t*)&out+2) = *((uint8_t*)&in+1);
  *((uint8_t*)&out+3) = *((uint8_t*)&in);
  return out;
}

/* --------- check TTY speed --------------------------- */
speed_t check_termios_speed(const int rate) {
  switch(rate) {
    case 50:	return B50;
    case 75:	return B75;
    case 150:	return B150;
    case 300:	return B300;
    case 600:	return B600;
    case 1200:	return B1200;
    case 2400:	return B2400;
    case 4800:	return B4800;
    case 9600:	return B9600;
    case 19200:	return B19200;
    case 38400:	return B38400;
    case 57600:	return B57600;
    case 115200: return B115200;
    default:	fprintf(stderr,"unsupported TTY speed\n");
  }
  return B0;
}

/* --------- NMEA XOR-Checksum ------------------------- */
short int nmea_xor_checksum(const char* text, const int len) {
  int i;
  unsigned char res = 0;
  if(len<2)	 return -1;
  if(text[0]!='$')	 return -1;
  if(text[len-1]!='*')	 return -1;
  for(i=1;i<=len-2;i++) {
    res^=(unsigned char) text[i];
  }
  return res;
}

/* --------- check and open TTY device ----------------- */
int check_open_tty(const char* ttyport) {
  int fd;
  /* --------- check tty port */
  struct stat buf;
  if (stat(ttyport,&buf)!=0) {
    fprintf(stderr,"%s: %s\n",ttyport,strerror(errno));
    return -1;
  }
  if (!S_ISCHR(buf.st_mode)) {
    fprintf(stderr,"%s is not a char device\n",ttyport);
    return -1;
  }

  /* --------- open tty port */
  fd = open(ttyport, O_RDWR|O_NONBLOCK|O_NOCTTY);
  if (fd<0) {
    fprintf(stderr,"%s: %s\n",ttyport,strerror(errno));
  }
  return fd;
}

/*-------- save old and set new TTY settings --------------*/
int set_termios(int fd, struct termios* oldtio_p, const speed_t termios_speed) {
  struct termios newtio;
  memset(&newtio, 0, sizeof(newtio));

  /* --------- save old and set new tty parameters */
  if( tcgetattr(fd,oldtio_p) == -1 ){
    perror("failed to save old tty state");
    return 1;
  }

  /* --- control flags */
  /* 8 bits/byte, ignore modem control lines */
  newtio.c_cflag |= CS8 | CLOCAL | CREAD;

  /* 8 bits/byte, ignore modem control lines, hardware flow control */
  /*  newtio.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS;	*/

  /* --- output flags */
  /* baud rate */
  cfsetospeed(&newtio,termios_speed);
  /* output processing off */
  newtio.c_oflag &= ~(OPOST);

  /* --- input flags */
  /* input speed same as output speed */
  cfsetispeed(&newtio,0);
  /* dont ignore input parity errors */
  /*  newtio.c_iflag &= ~IGNPAR;*/
  /* enable software flow control */
  /*  newtio.c_iflag |= (IXON | IXOFF);	*/
  /* disable software flow control */
  newtio.c_iflag &= ~(IXON | IXOFF);
  /* no SIGINT on BREAK, CR to NL off, input pariry check off, dont strip 8th bit */
  newtio.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON );

  /* --- local flags */
  /* set echo off, input non-canonical mode, no extended input processing, no signal chars */
  newtio.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

  /* --- control chars */
  /* inter-character timer unused, blocking read until 1 chars received */
  newtio.c_cc[VTIME] = 0;
  newtio.c_cc[VMIN] = 1;

  /* --- flush old bits out of the input buffer */
  if( tcflush(fd, TCIFLUSH) == -1 ){
    perror("flush tty");
    return 1;
  }

  /* --- write new port settings */
  if( tcsetattr(fd,TCSANOW,&newtio) == -1 ){
    perror("write new tty settings");
    return 1;
  }
  return 0;
}

/*-------- send some bytes --------------------------------*/
int serial_write(const int fd, const char* sendbuf, const unsigned int size){
  unsigned int i;
  if ((i=write(fd,sendbuf,size))!=size) {
    fprintf(stderr,"error: write %s\n",strerror(errno));
    return (-1);
  }
  return (i);
}

/*-------- nonblocking serial line read -------------------*/
int serial_read(const int fd, char* buf, const int size, const long int responsetime){
  int res, readcount=0, error=0;
  struct timeval tv;
  fd_set readfds;
  tv.tv_sec = (int)(responsetime/1000);
  tv.tv_usec = 1000*(responsetime-1000*(int)(responsetime/1000));
  while(!error && (size-readcount-1)>0 ){
    FD_ZERO(&readfds);
    FD_SET(fd,&readfds);
    if (select(fd+1,&readfds,NULL,NULL,&tv)==-1){
      fprintf(stderr, "problem select %s\n", strerror(errno));
      error=1;
    }
    if (!FD_ISSET(fd, &readfds)){
      buf[readcount]='\0';
      break;
    }else{
      res = read(fd,buf+readcount,size-readcount-1);
      if (res<0){
        fprintf(stderr,"cannot happen error: read: %s\n",strerror(errno));
        error=1;
      }
      tv.tv_sec = (int)(responsetime/1000);
      tv.tv_usec = 1000*(responsetime-1000*(int)(responsetime/1000));
      readcount+=res;
    }
  }
  return (error?-1:readcount);
}

/*-------- send rgm3800 commands --------------------------*/
int rgm3800_send(const int fd, const char* sendbuf){
  char sbuf[MAX_STRLEN];
  size_t len;
  int checksum;
  int res;
  len		= strlen(sendbuf);
  checksum	= nmea_xor_checksum(sendbuf,len);
  if (checksum<0 || checksum>255) return 1;
  sprintf(sbuf,"%s%2.2X\r\n",sendbuf,checksum);

  res		= ((len+4)==serial_write(fd, sbuf, len+4))?0:1;
  return res;
}

/*-------- parse response ---------------------------------*/
int rgm3800_parse_response(const char* buf, long int size, rgm3800_response_t* rs){
  char* bufp;

  rs->blocks=0;
  bufp = strstr(buf,"$LOG");
  while (bufp!=NULL) {
    int n;
    for(n=1;(int)(bufp-buf)+n+4<size;n++) {
	unsigned int checksum;
	if (1==sscanf((bufp+n),"*%2X\r\n",&checksum)) {
	  if(checksum==nmea_xor_checksum(bufp,n+1)) { rs->xor_valid[rs->blocks] = 1; }
	  else { rs->xor_valid[rs->blocks] = 0; continue; }
	  rs->blockp[rs->blocks]	= bufp;
	  rs->size[rs->blocks]	= n;
	  rs->blocks++;
	  bufp = strstr(bufp+n,"$LOG");
	  break;
	}
    }
    if ((int)(bufp-buf)+n+4>=size) break;
  }
  return rs->blocks;
}

/*-------- print response struct content ----------------*/
void rgm3800_debug_response(rgm3800_response_t* rs){
  int i;
  int total=0;
  for(i=0; i<rs->blocks; i++) {
    total+=rs->size[i];
    printf("%i %i %i\n",i, rs->size[i], rs->xor_valid[i] );
  }
  printf("total %i\n",total);
}

/*-------- read status from device into status struct -----*/
int rgm3800_get_status(const int fd, rgm3800_status_t* ss){
  unsigned int i;
  int error = 0;
  char cmdbuf[MAX_STRLEN];
  char buf[MAX_STRLEN];
  int	b[10];
  rgm3800_response_t rs;

  sprintf(cmdbuf,"$PROY108*");
  error	|= rgm3800_send(fd, cmdbuf);
  i	= serial_read(fd, buf, MAX_STRLEN-5, 300);

  rgm3800_parse_response(buf, i, &rs);
  if(rs.blocks<1){
    fprintf(stderr,"error: no response from device\n");
    return 1;
  }
  if(rs.xor_valid[0]!=1){
    fprintf(stderr,"error: checksum error\n");
    return 1;
  }
  i = sscanf(rs.blockp[0],"$LOG108,%i,%i,%i,%i,%i,%i,%i,%i,%i*%x\r\n",&b[0],&b[1],&b[2],&b[3],&b[4],&b[5],&b[6],&b[7],&b[8],&b[9]);
  if (i!=10) {
    fprintf(stderr,"error: bogous response for status request:\n%s\n",buf);
    return 1;
  }
  if(b[0]<0||b[0]>4) {
    fprintf(stderr,"error: wrong record mode %i\n",b[0]);
    return 1;
  }
  if(b[3]<0||b[3]>1) {
    fprintf(stderr,"error: wrong overwrite mode %i\n",b[0]);
    return 1;
  }

  ss->rectype	= b[0];
  ss->unknown1	= b[1];
  ss->unknown2	= b[2];
  ss->overwrite	= b[3];
  ss->unknown4	= b[4];
  ss->interval	= b[5];
  ss->receive	= b[6];
  ss->files	= b[7];
  ss->blocks	= b[8];

  return 0;
}

/*-------- get directory entry by file id -----------------*/
int rgm3800_get_dirent(const int fd, const unsigned int id, rgm3800_dir_entry_t* ds){

  char cmdbuf[MAX_STRLEN];
  char buf[MAX_STRLEN];
  char* bufp;
  int error = 0;
  int i	= 0;
  int ttl;
  int size, type, check;
  long int date_raw, offset;
  rgm3800_response_t rs;

  sprintf(cmdbuf,"$PROY101,%i*",id);
  error	= rgm3800_send(fd, cmdbuf);
  if(error) return 1;

  for (ttl=timeout/10;ttl>=0;ttl--) {
    i	+= serial_read(fd, buf + i, MAX_STRLEN-i-5, 10);
    rgm3800_parse_response(buf, i, &rs);
    if(rs.blocks<1){
      if(ttl>0) { continue; }
      else { fprintf(stderr,"error: no response from device\n"); return 1; }
    }
    if(rs.xor_valid[0]!=1){
      if(ttl>0) { continue; }
      else { fprintf(stderr,"error: checksum error\n"); return 1; }
    }
    bufp=strstr(rs.blockp[0],"$LOG101");
    if (bufp == NULL) {
      if(ttl>0) { continue; }
      else { fprintf(stderr,"error: bogous response for record %i: LOG token not found:\n%s\n",id,rs.blockp[0]); return 1; }
    }
    i = sscanf(bufp,"$LOG101,%li,%i,%i,%li*%X\r\n",&date_raw,&type,&size,&offset,&check);
    if (i!=5) {
      if(ttl>0) { continue; }
      else { fprintf(stderr,"error: bogous response for record %i: cannot parse LOG response:\n%s\n",id,rs.blockp[0]); return 1; }
    }
    break;
  }
  if(type<0||type>4) {
    fprintf(stderr,"error: wrong file type %i\n",type);
    return 1;
  }

  ds->id	= id;
  ds->date	= date_raw;
  ds->type	= type;
  ds->size	= size;
  ds->offset	= offset;
  return 0;
}

/*-------- write config to device -------------------------*/
int rgm3800_write_config(const int fd, const rgm3800_status_t* ss){
  unsigned int i;
  int error = 0;
  char cmdbuf[MAX_STRLEN];
  char buf[MAX_STRLEN];
  int	b[2];
  rgm3800_response_t rs;

  sprintf(cmdbuf,"$PROY104,0,%i,%i,%i*", ss->interval, ss->rectype, ss->overwrite);
  error	|= rgm3800_send(fd, cmdbuf);
  i	= serial_read(fd, buf, MAX_STRLEN-5, 100);

  rgm3800_parse_response(buf, i, &rs);
  if(rs.blocks<1){
    fprintf(stderr,"error: no response from device\n");
    return 1;
  }
  if(rs.xor_valid[0]!=1){
    fprintf(stderr,"error: checksum error\n");
    return 1;
  }

  i = sscanf(rs.blockp[0],"$LOG104,%i*%x\r\n",&b[0],&b[1]);
  if (i!=2) {
    fprintf(stderr,"error: bogous response for config request:\n%s\n",buf);
    return 1;
  }
  if(b[0]!=1) {
    fprintf(stderr,"error: config request failed\n");
    return 1;
  }
  return 0;
}

/*-------- print device status ----------------------------*/
int rgm3800_status(const int fd){
  const char* receive_text[] = {"time,long,lat", "time,long,lat,alt", "time,long,lat,alt,vel","time,long,lat,alt,vel,dist","time,long,lat,alt,vel,status","5","6"};
  const char* overwrite_text[] = {"overwrite when full", "stop when full","2","3"};
  const char* receive_status_text[] = {"other", "no RX", "RX active","2","3"};
  int error = 0;
  int receive_status_text_num = 0;
  rgm3800_status_t ss;

  error |= rgm3800_get_status(fd, &ss);
  if(error) return 1;
  switch (ss.receive) {
    case 0: receive_status_text_num	= 1; break;
    case 192: receive_status_text_num	= 2; break;
  }
  printf("receive type         %i (%s)\n",ss.rectype,receive_text[ss.rectype]);
  printf("overwrite mode       %i (%s)\n",ss.overwrite, overwrite_text[ss.overwrite]);
  printf("interval             %i seconds\n",ss.interval);
  printf("receive status       %i (%s)\n",ss.receive,receive_status_text[receive_status_text_num]);
  printf("stored files         %i\n",ss.files);
  printf("records in last file %i\n",ss.blocks);
  printf("unknown 1            %i\n",ss.unknown1);
  printf("unknown 2            %i\n",ss.unknown2);
  printf("unknown 4            %i\n",ss.unknown4);

  return error;
}


/*-------- print firmware version ----------------------------*/
int rgm3800_fwversion(fd){
  unsigned int i;
  int error = 0;
  char cmdbuf[MAX_STRLEN];
  char buf[MAX_STRLEN];

  sprintf(cmdbuf,"$PROY005*");
  error	|= rgm3800_send(fd, cmdbuf);
  i	= serial_read(fd, buf, MAX_STRLEN-5, 300);

  printf("%s\n",buf);
  return error;
}


/*-------- print GPS date ----------------------------*/
int rgm3800_date(fd){
  unsigned int i;
  int error = 0;
  char cmdbuf[MAX_STRLEN];
  char buf[MAX_STRLEN];
  rgm3800_response_t rs;
  int responsetype, td_Y, td_m, td_d, td_H, td_M, td_S, check;

  sprintf(cmdbuf,"$PROY003*");
  error	|= rgm3800_send(fd, cmdbuf);
  i	= serial_read(fd, buf, MAX_STRLEN-5, 300);

  rgm3800_parse_response(buf, i, &rs);
  if(rs.blocks<1){
    fprintf(stderr,"error: no response from device\n");
    return 1;
  }
  if(rs.xor_valid[0]!=1){
    fprintf(stderr,"error: checksum error\n");
    return 1;
  }

  i = sscanf(rs.blockp[0],"$LOG%3i,%4i%2i%2i,%2i%2i%2i*%x\n\n",&responsetype,&td_Y,&td_m,&td_d,&td_H,&td_M,&td_S,&check);
  if(i<8 && i>=2 &&( responsetype!=3 || td_Y==0 )) {
    fprintf(stderr,"error: device not synchronized\n");
    return 1;
  }

  if(i==8 && responsetype==3) {
    printf("%04i-%02i-%02i %02i:%02i:%02i\n",td_Y,td_m,td_d,td_H,td_M,td_S);
  } else {
    fprintf(stderr,"error: cannot parse date %s\n",buf);
    return 1;
  }
  return error;
}


/*-------- erase all data ----------------------------*/
int rgm3800_erase(fd){
  unsigned int i;
  int error = 0;
  char cmdbuf[MAX_STRLEN];
  char buf[MAX_STRLEN];
  rgm3800_response_t rs;
  int result = 0, check = 0;

  sprintf(cmdbuf,"$PROY109,-1*");
  error	|= rgm3800_send(fd, cmdbuf);
  i	= serial_read(fd, buf, MAX_STRLEN-5, 300);

  rgm3800_parse_response(buf, i, &rs);
  if(rs.blocks<1){
    fprintf(stderr,"error: no response from device\n");
    return 1;
  }
  if(rs.xor_valid[0]!=1){
    fprintf(stderr,"error: checksum error\n");
    return 1;
  }

  i = sscanf(rs.blockp[0],"$LOG109,%i*%x",&result,&check);
  if(i==2) {
    if(result!=1) {
      fprintf(stderr,"error: erase result was %i\n",result);
      return 1;
    }
  } else {
    fprintf(stderr,"error: bad response from device\n");
    return 1;
  }
  return error;
}


/*-------- list directory ---------------------------------*/
int rgm3800_ls(const int fd){
  unsigned int n;
  int error = 0;
  rgm3800_dir_entry_t ds;
  rgm3800_status_t ss;

  error |= rgm3800_get_status(fd, &ss);
  if(error) return 1;

  error |= rgm3800_get_dirent(fd, ss.files-1, &ds);
  if(error) return 1;

  printf("%i files in %li bytes\n",ss.files, ds.offset + ds.size*rgm3800_blocksize[ds.type]);

  for (n=0; n<ss.files; n++) {
    error |= rgm3800_get_dirent(fd, n, &ds);
    if (error) { continue; }
    printf("%7i %li %i %7i %8li\n", ds.id, ds.date, ds.type, ds.size, ds.offset);
  }
  return error;
}

/*-------- parse and set config params ---------------------------------*/
int rgm3800_set(const int fd, const char* arg){
  int error = 0;
  char buf[MAX_STRLEN];
  char* bufp;
  rgm3800_status_t ss;
  int interval;

  error |= rgm3800_get_status(fd, &ss);
  if(error) return 1;

  strncpy(buf,arg,MAX_STRLEN);
  buf[MAX_STRLEN-1]='\0';
  bufp	= strtok(buf,",");
  while (bufp!=NULL) {
    if (*(bufp+1)!='='||*(bufp+2)=='\0' ) {
      fprintf(stderr,"error: set argument syntax error\n");
      return 1;
    }
    switch(*bufp) {
      case 'i':
	if (sscanf((bufp+2),"%i",&interval)!=1) {
	  fprintf(stderr,"error: interval argument must be numeric\n");
	  return 1;
	}
	ss.interval	= interval;
	break;
     case 'f':
	switch(*(bufp+2)) {
	  case 'o': ss.overwrite = 0; break;
	  case 's': ss.overwrite = 1; break;
	  default: fprintf(stderr,"error: invalid parameter for f argument\n");	return 1;
	}
	break;
     case 't':
	switch(*(bufp+2)) {
	  case '0':
	  case 'm': ss.rectype = 0; break;
	  case '1':
	  case 'a': ss.rectype = 1; break;
	  case '2':
	  case 'v': ss.rectype = 2; break;
	  case '3': ss.rectype = 3; break;
	  case '4': ss.rectype = 4; break;
	  default: fprintf(stderr,"error: invalid parameter for t argument\n");	return 1;
	}
	break;
     default:
	fprintf(stderr,"error: unknown set argument\n");
	return 1;
    }
    bufp	= strtok(NULL,",");
  }

  error |= rgm3800_write_config(fd, &ss);
  return error;
}

/*-------- parse response ---------------------------------*/
int rgm3800_rs2gs(rgm3800_geo_entry_t* gs, const rgm3800_response_t* rs, const rgm3800_dir_entry_t* ds, const int gs_offset, const int gs_size) {
  int i	= gs_offset;
  int block;

  for (block=0; block<rs->blocks; block++) {
    int k;
    char* bufp;
    bufp = strstr(rs->blockp[block],"$LOG102,\0\0<");
    if (bufp==NULL || (int)(bufp-rs->blockp[block])> rs->size[block]) {
      fprintf(stderr,"warn: ignoring bogous block %i\n",block);
      continue;
    }
    for (k=11;k<rs->size[block];k+=rgm3800_blocksize[ds->type]) {
	uint32_t raw_lat, raw_lon, raw_alt, raw_vel, raw_dir;

	if(print_raw_f==1) {
	  printf("# %u %u\n",block,k);
	  int m;
	  for (m=0;m<rgm3800_blocksize[ds->type];m++) {
	    printf("%2.2X ",(unsigned char)*(bufp+k+m));
	  }
	  printf("\n");
	}

	/* what does this first char mean? */
	if (0x1!=*(char*)(bufp+k)) {
	  fprintf(stderr,"warn: ignoring bogous line in block %i\n",block);
	  continue;
	}

	gs[i].type	= ds->type;
	gs[i].date	= ds->date;

	/* FIXME: this may be endianess dependant */
	gs[i].time_h	= *(uint8_t*)(bufp+k+1);
	gs[i].time_m	= *(uint8_t*)(bufp+k+2);
	gs[i].time_s	= *(uint8_t*)(bufp+k+3);
	raw_lat		= ntohl(bswap32(*(uint32_t*)(bufp+k+4)));
	raw_lon		= ntohl(bswap32(*(uint32_t*)(bufp+k+8)));
	gs[i].lat	= *(float*)(&raw_lat);
	gs[i].lon	= *(float*)(&raw_lon);
	if (ds->type>=1) {
	  raw_alt	= ntohl(bswap32(*(uint32_t*)(bufp+k+12)));
	  gs[i].alt	= *(float*)(&raw_alt);
	}
	if (ds->type>=2) {
	  raw_vel	= ntohl(bswap32(*(uint32_t*)(bufp+k+16)));
	  gs[i].vel	= *(float*)(&raw_vel);
	}
	if (ds->type>=3) {
	  gs[i].dist	= ntohl(bswap32(*(uint32_t*)(bufp+k+20)));
	}
	if (ds->type>=4) {
	  int sat_record;
	  for(sat_record=0;sat_record<12;sat_record++) {
	    gs[i].satid[sat_record]	= *(uint8_t*)(bufp+k+32+sat_record*2);
	    gs[i].satsnr[sat_record]	= *(uint8_t*)(bufp+k+33+sat_record*2);
	  }
	  raw_dir		= ntohl(bswap32(*(uint32_t*)(bufp+k+56)));
	  gs[i].direction	= *(float*)(&raw_dir);
	}

	/* blanking the lowest 8 bits in the velocity mantice without data loss */
	/*
	char* cp;
	cp		= (char*)&gs[i].vel;
	*cp		= 0xff;
	*/
	i++;
    }
  }
  return i-gs_offset;
}

/*-------- generate and print NMEA data -----------------*/
void rgm3800_print_nmea(const rgm3800_geo_entry_t* gs){
  int lat_deg, lon_deg, checksum;
  int date_y, date_m, date_d;
  double lat, lon, alt, vel, dir;
  char ns, ew;
  char buf[MAX_STRLEN];

  /* --- lon and lat correction factor
   * radians (2*pi) to degrees (360)
   * thanks Karsten Petersen for this hint
   */
  const double lon_lat_corr=57.295779513082;

  /* --- speed-correction-factor,
   * this looks like km/h to knots (nautical miles per hour) */
  const double vel_corr=1.0/1.852;

  lat		= lon_lat_corr*gs->lat;
  lon		= lon_lat_corr*gs->lon;

  /* this is just a guess, I have only data from germany available 50N 7E */
  if (lat>=0) { ns='N'; } else { ns='S'; lat=-lat;}
  if (lon>=0&&lon<=180) { ew='E'; } else { ew='W'; lon=-lon;}

  /* conversion to stupid deg/minutes format */
  lat_deg	= (int)lat;
  lon_deg	= (int)lon;
  lat		-= lat_deg;
  lon		-= lon_deg;
  lat		*= 0.6;
  lon		*= 0.6;
  lat		+= lat_deg;
  lon		+= lon_deg;
  lat		*= 100;
  lon		*= 100;

  date_d	= gs->date%100;
  date_m	= (gs->date%10000-date_d)/100;
  date_y	= (gs->date%1000000-date_d-date_m)/10000;

  if(gs->type<1) { alt=0; } else { alt	= gs->alt; }

  /* speed in knots */
  if(gs->type<2) { vel=0; } else { vel	= vel_corr*gs->vel; }
  sprintf(buf,"$GPGGA,%2.2i%2.2i%2.2i.000,%09.4f,%c,%010.4f,%c,1,00,,%06.1f,M,0.0,M,,0000*",gs->time_h, gs->time_m, gs->time_s, lat, ns, lon, ew, alt);
  checksum	= nmea_xor_checksum(buf,strlen(buf));
  if (checksum>=0 && checksum<=255) { printf("%s%2.2X\n",buf,checksum); }

  if(gs->type>=4) {
    int gpgsv_num=0;
    for(gpgsv_num=0;gpgsv_num<=2;gpgsv_num++) {
      sprintf(buf,"$GPGSV,3,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i*",gpgsv_num+1,12,
	gs->satid[gpgsv_num*4],45,gpgsv_num*30,gs->satsnr[gpgsv_num*4],
	gs->satid[gpgsv_num*4+1],45,(gpgsv_num+1)*30,gs->satsnr[gpgsv_num*4+1],
	gs->satid[gpgsv_num*4+2],45,(gpgsv_num+2)*30,gs->satsnr[gpgsv_num*4+2],
	gs->satid[gpgsv_num*4+3],45,(gpgsv_num+3)*30,gs->satsnr[gpgsv_num*4+3]);
      checksum	= nmea_xor_checksum(buf,strlen(buf));
      if (checksum>=0 && checksum<=255) { printf("%s%2.2X\n",buf,checksum); }
    }
  }

  if(gs->type<4) { dir=15.15; } else { dir	= gs->direction; }
  sprintf(buf,"$GPRMC,%2.2i%2.2i%2.2i.000,A,%09.4f,%c,%010.4f,%c,%06.2f,%6.2f,%2.2i%2.2i%2.2i,,,E*",gs->time_h, gs->time_m, gs->time_s, lat, ns, lon, ew, vel, dir, date_d, date_m, date_y);
  checksum	= nmea_xor_checksum(buf,strlen(buf));
  if (checksum>=0 && checksum<=255) { printf("%s%2.2X\n",buf,checksum); }

  if(gs->type>=3) {
    sprintf(buf,"$RTDIST,A,%1.1i,%3.1f,%3.1f,%3.1f,%i*", 3, 0.0, 0.0, 0.0, gs->dist);
    checksum	= nmea_xor_checksum(buf,strlen(buf));
    if (checksum>=0 && checksum<=255) { printf("%s%2.2X\n",buf,checksum); }
  }
}

/*-------- download data ---------------------------------*/
int rgm3800_get(const int fd, const unsigned int id){
  unsigned int j;
  unsigned int datasets = 0;
  unsigned int packetsize = 456;	/* initial amount of datasets to fetch */
  int error = 0;
  int ttl;
  char cmdbuf[MAX_STRLEN];
  char buf[131072];			/* FIXME */
  rgm3800_geo_entry_t gs_buf[5462];	/* FIXME */
  rgm3800_status_t ss;
  rgm3800_dir_entry_t ds;
  rgm3800_response_t rs;

  error |= rgm3800_get_status(fd, &ss);
  if(error) return 1;
  if(ss.files<=id) { fprintf(stderr,"error: no such file id %i\n", id); return 1; }

  error |= rgm3800_get_dirent(fd, id, &ds);
  if(error) return 1;

  if(ds.type<0||ds.type>4) { fprintf(stderr,"error: unsupported record format %i\n", ds.type); return 1; }	

  while (datasets<ds.size) {
    int i	= 0;
    if (packetsize>(ds.size-datasets)) { packetsize = (ds.size-datasets); }
    sprintf(cmdbuf,"$PROY102,%li,%i,%i*",ds.offset + datasets*rgm3800_blocksize[ds.type] ,ds.type,packetsize);
    error	|= rgm3800_send(fd, cmdbuf);
    for (ttl=timeout/25;ttl>=0;ttl--) {
      int m	= 0;
      i	+= serial_read(fd, buf + i, 131067-i, 25);

      rgm3800_parse_response(buf, i, &rs);
/*    rgm3800_debug_response(&rs); */

      if(rs.blocks<1){
        if(ttl>0) { continue; }
        else { fprintf(stderr,"error: no response from device\n"); return 1; }
      }
      for (j=0; j<rs.blocks; j++) {
	if(rs.xor_valid[j]!=1){
	  if(ttl>0) { continue; }
	  else { fprintf(stderr,"error: checksum error in file %i block %i\n", id, j); return 1; }
	}
      }
      m = rgm3800_rs2gs(gs_buf, &rs, &ds, datasets, ds.size);
      if (m==packetsize || datasets+m==ds.size) {
	datasets+=m;
	break;
      } else {
	if(ttl>0) { continue; }
	else {
/*	  fprintf(stderr,"# file %i size %i at %i using packet size %i got only %i\n", id, ds.size, datasets, packetsize, m); */
	  if(packetsize/2<m) { packetsize = m; }
	  else { packetsize /= 2; }
	}
      }
    }
  }

  if(print_raw_f!=1) {
    int i;
    for (i=0; i<datasets; i++) {
      rgm3800_print_nmea(&(gs_buf[i]));
    }
  }

  return 0;
}

/*-------- print all track data -------------------------*/
int rgm3800_dump(const int fd){
  int error = 0;
  int i;
  rgm3800_status_t ss;

  error |= rgm3800_get_status(fd, &ss);
  if(error) return 1;

  for (i=0;i<ss.files;i++) {
    if (0!=rgm3800_get(fd, i)) { return 1; }
  }
  return 0;
}

/* ------------------------------------------------------------------------*/
int main (int argc, char* argv[])
{
    int c;
    int disable_f	= 0;
    int output_f	= 0;
    int error		= 0;

    const char default_ttyport[]	= "/dev/ttyUSB0";
    const char* prog;
    const char* output_file;
    const char* ttyport;
     int fd;
    int baudrate;
    speed_t termios_speed;

#if defined(HAVE_GETOPT_H)
    static struct option long_options[] =
    {
	{  "baudrate",	1,  NULL,  'b'  },
	{  "timeout",	1,  NULL,  't'  },
	{  "port",	1,  NULL,  'p'  },
	{  "output",	1,  NULL,  'o'  },
	{  "help",	0,  NULL,  'h'  },
	{  "version",	0,  NULL,  'v'  },
	{  NULL,        0,  NULL,   0   }
    };
#endif
    /*--------- get argv */
    prog	= argv[0];
    /*--------- defaults */
/*    output_fp	= stdout; */
    ttyport	= default_ttyport;
    baudrate	= 115200;
    print_raw_f	= 0;
    timeout	= 1500;
    /*--------- process all options */
    while (1){
#if defined(HAVE_GETOPT_H)
	int option_index = 0;
	c = getopt_long (argc, argv, "b:t:p:o:rhv", long_options, &option_index);
#else
	c = getopt (argc, argv, "b:t:p:o:rhv");
#endif
	if (c == -1) break;
	switch (c){
	    case 0: if (optarg) fprintf (stderr, "%s: bug: no code for option with arg %s\n", prog, optarg); break;
	    case 'b':  baudrate		= atoi(optarg);			break;
	    case 't':  timeout		= atoi(optarg);			break;
	    case 'p':  ttyport		= optarg;			break;
	    case 'o':  output_file	= optarg;	output_f = 1;	break;
	    case 'r':  print_raw_f	= 1;				break;
	    case 'h':  disable_f = 1; usage(prog);			break;
	    case '?':  disable_f = 1; usage(prog);			break;
	    case 'v':  disable_f = 1; fprintf (stderr, "%s\n", version);break;
	    default:   disable_f = 1; usage(prog);			break;
	}
    }
    if(disable_f==0){
	if ((termios_speed = check_termios_speed(baudrate))==B0)	return 1;
	if ((fd = check_open_tty(ttyport))<0)				return 1;
	if (set_termios(fd, &oldtio_l, termios_speed)==-1)		return 1;
	/* --------- port is ready now */

	/* --------- command parser loop */
	if (optind >= argc) { usage(prog); }
	while (optind < argc && error==0) {
	  /* --- parse input */
	  if (strlen(argv[optind])>MAX_STRLEN) {
	    fprintf(stderr, "%s: warning: argument size exceeded, argument %i ignored\n", prog, optind);
	  }
	  if (0==strcmp("help",argv[optind])) {
	    usage(prog);
	    optind++;
	    continue;
	  }
	  if (0==strcmp("ls",argv[optind])) {
	    error = rgm3800_ls(fd);
	    optind++;
	    continue;
	  }
	  if (0==strcmp("dump",argv[optind])) {
	    error = rgm3800_dump(fd);
	    optind++;
	    continue;
	  }
	  if (0==strcmp("status",argv[optind])) {
	    error = rgm3800_status(fd);
	    optind++;
	    continue;
	  }
	  if (0==strcmp("erase",argv[optind])) {
	    error = rgm3800_erase(fd);
	    optind++;
	    continue;
	  }
	  if (0==strcmp("fwversion",argv[optind])) {
	    error = rgm3800_fwversion(fd);
	    optind++;
	    continue;
	  }
	  if (0==strcmp("date",argv[optind])) {
	    error = rgm3800_date(fd);
	    optind++;
	    continue;
	  }
	  if (0==strcmp("set",argv[optind])) {
	    if(optind+1 >= argc) {
	      fprintf(stderr, "set command needs an argument\n");
	      error = 1;
	      break;
	    }
	    error = rgm3800_set(fd,argv[optind+1]);
	    optind	+= 2;
	    continue;
	  }
	  if (0==strcmp("get",argv[optind])) {
	    int id=0;
	    if(optind+1 >= argc) {
	      fprintf(stderr, "get command needs an argument\n");
	      error = 1;
	      break;
	    }
	    if (sscanf(argv[optind+1],"%i",&id)!=1) {
	      fprintf(stderr, "get argument must be numeric\n");
	      error = 1;
	      break;
	    }
	    error = rgm3800_get(fd,id);
	    optind	+= 2;
	    continue;
	  }
	  fprintf(stderr, "%s: unknown command \"%s\", use -h for help\n", prog, argv[optind]);
	  break;
	}
	/* --------- restore old tty parameters, cleanup */
	tcsetattr(fd,TCSANOW,&oldtio_l);
	close(fd);
    }
    return error;
}
