/*
 * drcSerial.c:
 *	Extend wiringPi with the DRC Serial protocol (e.g. to Arduino)
 *	Copyright (c) 2013 Gordon Henderson
 ***********************************************************************
 * This file is part of wiringPi:
 *	https://projects.drogon.net/raspberry-pi/wiringpi/
 *
 *    wiringPi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    wiringPi 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 Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with wiringPi.
 *    If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>

#include "wiringPi.h"
#include "wiringSerial.h"

#include "drcSerial.h"

#ifndef	TRUE
#  define	TRUE	(1==1)
#  define	FALSE	(1==2)
#endif


/*
 * myPinMode:
 *	Change the pin mode on the remote DRC device
 *********************************************************************************
 */

static void myPinMode (struct wiringPiNodeStruct *node, int pin, int mode)
{
  /**/ if (mode == OUTPUT)
    serialPutchar (node->fd, 'o') ;       // Input
  else if (mode == PWM_OUTPUT)
    serialPutchar (node->fd, 'p') ;       // PWM
  else
    serialPutchar (node->fd, 'i') ;       // Default to input

  serialPutchar (node->fd, pin - node->pinBase) ;
}


/*
 * myPullUpDnControl:
 *	ATmegas only have pull-up's on of off. No pull-downs.
 *********************************************************************************
 */

static void myPullUpDnControl (struct wiringPiNodeStruct *node, int pin, int mode)
{

// Force pin into input mode

  serialPutchar (node->fd, 'i' ) ;
  serialPutchar (node->fd, pin - node->pinBase) ;

  /**/ if (mode == PUD_UP)
  {
    serialPutchar (node->fd, '1') ;
    serialPutchar (node->fd, pin - node->pinBase) ;
  }
  else if (mode == PUD_OFF)
  {
    serialPutchar (node->fd, '0') ;
    serialPutchar (node->fd, pin - node->pinBase) ;
  }
}


/*
 * myDigitalWrite:
 *********************************************************************************
 */

static void myDigitalWrite (struct wiringPiNodeStruct *node, int pin, int value)
{
  serialPutchar (node->fd, value == 0 ? '0' : '1') ;
  serialPutchar (node->fd, pin - node->pinBase) ;
}


/*
 * myPwmWrite:
 *********************************************************************************
 */

static void myPwmWrite (struct wiringPiNodeStruct *node, int pin, int value)
{
  serialPutchar (node->fd, 'v') ;
  serialPutchar (node->fd, pin - node->pinBase) ;
  serialPutchar (node->fd, value & 0xFF) ;
}


/*
 * myAnalogRead:
 *********************************************************************************
 */

static int myAnalogRead (struct wiringPiNodeStruct *node, int pin)
{
  int vHi, vLo ;

  serialPutchar (node->fd, 'a') ;
  serialPutchar (node->fd, pin - node->pinBase) ;
  vHi = serialGetchar (node->fd) ;
  vLo = serialGetchar (node->fd) ;

  return (vHi << 8) | vLo ;
}


/*
 * myDigitalRead:
 *********************************************************************************
 */

static int myDigitalRead (struct wiringPiNodeStruct *node, int pin)
{
  serialPutchar (node->fd, 'r') ; // Send read command
  serialPutchar (node->fd, pin - node->pinBase) ;
  return (serialGetchar (node->fd) == '0') ? 0 : 1 ;
}


/*
 * drcSetup:
 *	Create a new instance of an DRC GPIO interface.
 *	Could be a variable nunber of pins here - we might not know in advance
 *	if it's an ATmega with 14 pins, or something with less or more!
 *********************************************************************************
 */

int drcSetupSerial (const int pinBase, const int numPins, const char *device, const int baud)
{
  int fd ;
  int ok, tries ;
  time_t then ;
  struct wiringPiNodeStruct *node ;

  if ((fd = serialOpen (device, baud)) < 0)
    return wiringPiFailure (WPI_ALMOST, "Unable to open DRC device (%s): %s", device, strerror (errno)) ;

  delay (10) ;	// May need longer if it's an Uno that reboots on the open...

// Flush any pending input

  while (serialDataAvail (fd))
    (void)serialGetchar (fd) ;

  ok = FALSE ;
  for (tries = 1 ; (tries < 5) && (!ok) ; ++tries)
  {
    serialPutchar (fd, '@') ;		// Ping
    then = time (NULL) + 2 ;
    while (time (NULL) < then)
      if (serialDataAvail (fd))
      {
        if (serialGetchar (fd) == '@')
        {
          ok = TRUE ;
          break ;
        }
      }
  }

  if (!ok)
  {
    serialClose (fd) ;
    return wiringPiFailure (WPI_FATAL, "Unable to communicate with DRC serial device") ;
  }

  node = wiringPiNewNode (pinBase, numPins) ;

  node->fd              = fd ;
  node->pinMode         = myPinMode ;
  node->pullUpDnControl = myPullUpDnControl ;
  node->analogRead      = myAnalogRead ;
  node->digitalRead     = myDigitalRead ;
  node->digitalWrite    = myDigitalWrite ;
  node->pwmWrite        = myPwmWrite ;

  return 0 ;
}
