/*
  charge - a simple tool to enble the built in charger in
  Mobile Action USB data cables

  Copyright (C) 2006 Martin Leopold <leopold@diku.dk>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) 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.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>

struct termios oldkey, newkey;       //place for old and new port settings for keyboard teletype

int read_fd(int fd,
	    unsigned char *bufstart,
	    unsigned int bufs) {

  fd_set rfds;
  struct timeval timeout;
  unsigned char * bufptr;
  unsigned int n;

  FD_ZERO(&rfds);
  FD_SET(fd, &rfds);
  timeout.tv_sec = 3;
  timeout.tv_usec = 0;

  n = select(fd+1, &rfds, NULL, NULL, &timeout);
  if (n < 0) {
    perror("select failed");
    return(errno);
  } else if (n == 0) {
    perror("TIMEOUT");
    return(-1);
  }

  bufptr = bufstart;
  while ((n = read(fd, bufptr, bufstart + bufs - bufptr - 1)) > 0) {
    bufptr += n;
  }
  (*bufptr) = (char) 0;
  printf("Read %i bytes: %s ", bufptr-bufstart, bufptr);
  for(n=0 ; n<bufptr-bufstart ; n++)
    printf("%#hhx ", (unsigned char) bufstart[n]);
  printf("\n");

}

void set_flags(unsigned int tty,
	       unsigned int clear,
	       unsigned int set) {
  // This is done w. two IOCTLS in charger.exe
  unsigned int flags;

  ioctl(tty, TIOCMGET, &flags);
  flags &= ~clear; // Clr bits from and
  flags |= set; // Set 
  ioctl(tty, TIOCMSET, &flags);
  tcflush(tty, TCIOFLUSH);
}

void set_rate(unsigned int tty,
	      unsigned int rate) {
  
  struct termios key;

  tcgetattr(tty,&key);
  cfsetispeed(&key, rate);
  cfsetospeed(&key, rate);
  tcsetattr(tty,TCSANOW,&key);
  tcflush(tty, TCIOFLUSH);

}


main(int argc, char *argv[]) {
  int tty, flags, n;
  unsigned char strn_1[5]={'U','U','U','U',0x5};
  unsigned char strn_2[5]={'U','U','U','U',0x6};
  unsigned char strn_3[5]={'U','U','U','U',0x7};
  unsigned char strn_4[16]={0xDB, 0x68, 0xD2, 0xD3, 0xC0, 0x1D, 0xCB, 0xE3, 
  			    0xE5, 0xF1, 0xC2, 0xDF, 0x88, 0x7D, 0x00, 0xC1};
  // This string was observed during a different snoop-run
  //unsigned char strn_4[16]={0x1B, 0xE2, 0xB9, 0x24, 0xC4, 0x2E, 0x3F, 0x8F,
  //			    0xCA, 0x84, 0x46, 0x9E, 0x6C, 0x77, 0x21, 0x2C};

  char readbuf[50];
  char ATE0[6] = {0xd, 'A', 'T', 'E', '0', 0xd};
  char ATZ[5] = {0xd, 'A', 'T', 'Z', 0xd};
  char *ttydevice;
   
  if (argc != 1) {
     ttydevice = argv[1];
  } else {
     ttydevice = "/dev/ttyUSB0";
  }
   
  tty = open(ttydevice, O_RDWR | O_NOCTTY | O_NONBLOCK);
  fcntl(tty, F_SETFL, 0);

  if (tty == -1)
    {
      /*
       * Could not open the port.
       */
       fprintf(stderr, "open_port: Unable to open tty %s\n", ttydevice);
       return 1;
    }

  tcgetattr(tty,&oldkey); // save current port settings

  //charger.exe sets each as an ioctl
  //XON/XOFF flow control, 8 databits, 1 stop bit, parity none
  /*
  newkey.c_cflag =  CLOCAL | CREAD |
    B9600 | // Set baud rate
    CS8 | // 8 data bits
    IXON | IXOFF| IXANY & // Enable software flow control
    ~CSTOPB; // 1 stop bit
  */

  newkey.c_cflag =  CLOCAL | CREAD |
    CS8; // 8 data bits

  cfsetispeed(&newkey, B115200);
  cfsetospeed(&newkey, B115200);

  newkey.c_iflag = IGNPAR; //set input mode (ignore parity, non-canonical, no echo,...)
  newkey.c_oflag = 0;
  newkey.c_lflag = 0; 

  // Mimics "charger.exe" settings
  // NB. Timeouts are ignored in canonical input mode or when the NDELAY option is set 
  newkey.c_cc[VMIN]  = 0;   /* blocking read until x chars received */
  newkey.c_cc[VTIME] = 10;  /* inter-character timer unused in 1/10 s */

  // Mimics the settings of the structure SERIAL_CHARS in Windows as best I can
  newkey.c_cc[VSTART] = 11; //XON
  newkey.c_cc[VSTOP]  = 13; //OFF
  newkey.c_cc[VEOF]   = 0;
  newkey.c_cc[VQUIT]  = 0;  //No equal to Windows 'ErrorChar'
  newkey.c_cc[VERASE] = 0;
  newkey.c_cc[VINTR]  = 0;  //'EventChar'

  tcsetattr(tty,TCSANOW,&newkey);
  tcflush(tty, TCIOFLUSH);

  /*********************************************************************
   *
   * Following is the sequence of operations that turns the cable into
   * 115200 baud serial device.
   *
   * The sequence makes little sense and has been directly reverse
   * engineered from the Windows tool.
   *
   * The sleeps have been inserted by a bit of experimentation and
   * some times seem to be essential
   *
   *********************************************************************/
   
  
  /*****
    Start by flipping RTS/DTR a bit
   *****/
  set_flags(tty, TIOCM_RTS | TIOCM_DTR, 0); // Clr DTR and RTS
  usleep(5);
  set_flags(tty, TIOCM_RTS, TIOCM_DTR);  // Set DTR
  usleep(5);
  set_flags(tty, 0, TIOCM_DTR | TIOCM_RTS); // Set RTS
  usleep(5);

  /*****
    Send ATE0
   *****/
  n = write(tty, ATE0,6);
  if (n < 0)
    fputs("write() of 6 bytes failed!\n", stderr);
  tcflush(tty, TCIOFLUSH);

  usleep(5);
  set_rate(tty, B9600); // Set rate to 9600
  usleep(5);
  set_flags(tty, TIOCM_DTR, TIOCM_RTS); // Clr DTR
  usleep(5);

  /*****
    Send binary string: UUUU0x5
   *****/
  n = write(tty, strn_1, sizeof(strn_1));
  if (n < 0)
    fputs("write() failed!\n", stderr);
  tcflush(tty, TCIOFLUSH);

  /*****
    Wait for MA-CABLE string
   *****/
  read_fd(tty, readbuf, sizeof(readbuf));
  usleep(5);
  set_flags(tty, TIOCM_DTR, TIOCM_RTS); // Clr DTR
  usleep(100);

  /*****
    Send binary string: UUUU0x6
   *****/
  n = write(tty, strn_2, sizeof(strn_2));
  if (n < 0)
    fputs("write() failed!\n", stderr);
  tcflush(tty, TCIOFLUSH);

  /*****
    Wait for 55 A5 06 03 00 00 01 FF 06
   *****/
  read_fd(tty, readbuf, sizeof(readbuf));// 
  usleep(10);
  set_flags(tty, TIOCM_DTR, TIOCM_RTS); // Clr DTR
  usleep(10);

  /*****
    Send binary string: UUUU0x7
   *****/
  n = write(tty, strn_3, sizeof(strn_3));
  if (n < 0)
    fputs("write() failed!\n", stderr);
  tcflush(tty, TCIOFLUSH);
  usleep(10);

  /*****
    Send long binary string
   *****/
  n = write(tty, strn_4, sizeof(strn_4));
  if (n < 0)
    fputs("write() failed!\n", stderr);
  tcflush(tty, TCIOFLUSH);

  /*****
    Wait for long binary string
   *****/
  read_fd(tty, readbuf, sizeof(readbuf));
  usleep(100);
  set_rate(tty, B115200); // Set rate to 115200
  usleep(100);
  set_flags(tty, 0, TIOCM_DTR | TIOCM_RTS); // Set DTR

  usleep(500000); // Has to be a substantial wait..

  /*****
    Send ATZ
   *****/
  n = write(tty, ATZ, sizeof(ATZ));
  if (n < 0)
    fputs("write() failed!\n", stderr);
  tcflush(tty, TCIOFLUSH);
  printf("Sent ATZ %i\n", n);

  /*****
    Wait for OK
   *****/
  read_fd(tty, readbuf, sizeof(readbuf));

  ioctl(tty, TCFLSH);
  //  tcsetattr(tty,TCSANOW,&oldkey);
  close(tty);
}

