/**************************************************************************************************
*
*  uDrive
*
*   Version:          1.0.0 - Mai 2009
*   Author:           Etienne Ribeiro    / tutorial assistant caad      /  eribeiro[at]ethz.ch
*   Supervisor:       Christoph Wartmann / chair for caad - ETH Zürich  /  wartmann[at].arch.ethz.ch
*
*   Desc:             Library to write string-, int- and long-values to an SD-card using uDrive and a
*                     FAT16 formated SD-Card.
*                     Note: You need to upload the latest firmware to your uDrive (this library was
*                           tested with G1-DOS-r07.PmmC released at 26-Apr-2009)
*                     Note: This library uses NewSoftSerial. Make sure you get the latest version at
*                           least V8. Visit: http://arduiniana.org/libraries/NewSoftSerial/
*                     See: http://www.4dsystems.com.au
*
*   Not implemented:  - Low level disk drive commands
*                     - DOS: Reading from uDrive
*                     - DOS: Delete file
*                     - DOS: List directory
*                     - Device info
*
*   Functions:        - boolean uDrive_Initialize (int baud)
*                         needs to be called at startup
*                         returns true if successfull
*                     - boolean uDrive_LoadDisk ()
*                         needs to be called at startup
*                         returns true if successfull
*                     - boolean uDrive_writeFile (char* Filename, int Value)
*                         Writing int-values to a file on a FAT16 formated uDrive. If the file doesn't exist, it will be created.
*                         returns true if successfull
*                     - boolean uDrive_writeFile (char* Filename, int Value, boolean appendMode)
*                         Writing int-values to a file on a FAT16 formated uDrive. If the file doesn't exist, it will be created.
*                         appendMode: if true, values will be append to existing data, if false file will be overritten.
*                         returns true if successfull
*                     - boolean uDrive_writeFile (char* Filename, long Value)
*                         Writing long-values to a file on a FAT16 formated uDrive. If the file doesn't exist, it will be created.
*                         returns true if successfull
*                     - boolean uDrive_writeFile (char* Filename, long Value, boolean appendMode)
*                         Writing long-values to a file on a FAT16 formated uDrive. If the file doesn't exist, it will be created.
*                         appendMode: if true, values will be append to existing data, if false file will be overritten.
*                         returns true if successfull
*                     - boolean uDrive_writeFile (char* Filename, char* Data)
*                         Writing string-values to a file on a FAT16 formated uDrive. If the file doesn't exist, it will be created.
*                         returns true if successfull
*                     - boolean uDrive_writeFile (char* Filename, char* Data, long Filesize, boolean appendMode)
*                         Writing string-values to a file on a FAT16 formated uDrive. If the file doesn't exist, it will be created.
*                         appendMode: if true, values will be append to existing data, if false file will be overritten.
*                         returns true if successfull
*                     - int uDrive_LengthOfString (char* data)
*                         Get the length of a string.
*                         returns -1 if string too long.
*                     - int uDrive_LengthOfString (char* data, int abort)
*                         Get the length of a string.
*                         abort: max possible length.
*                         returns -1 if string too long.
*                     - int uDrive_Parse (int Value, char* ret)
*                         Parse an int to string
*                         ret: buffer to return the string value. Note: needs to be a char-array larger then the expected return value.
*                              example: char buffer[10]
*                         returns the length of the string
*                     - int uDrive_Parse (int Value, char* ret, int base)
*                         Parse an int to string
*                         ret: buffer to return the string value. Note: needs to be a char-array larger then the expected return value.
*                              example: char buffer[10]
*                         base: 10 for DEC, ...
*                         returns the length of the string
*                     - int uDrive_Parse (long Value, char* ret)
*                         Parse a long to string
*                         ret: buffer to return the string value. Note: needs to be a char-array larger then the expected return value.
*                              example: char buffer[10]
*                         returns the length of the string
*                     - int uDrive_Parse (long Value, char* ret, int base)
*                         Parse a long to string
*                         ret: buffer to return the string value. Note: needs to be a char-array larger then the expected return value.
*                              example: char buffer[10]
*                         base: 10 for DEC, ...
*                         returns the length of the string
*                     - boolean uDrive_HasErrors ()
*                         can be called to find out, if an error occured while writing.
*
*   Pins:             (starting from left, with chip on top)
*                     Pin 1: Reset
*                     Pin 2: Gnd
*                     Pin 3: Rx (100 to 220 Ohms resister is needed at voltages higher then 3.6V, however my uDrive works without at 5V)
*                     Pin 4: Tx
*                     Pin 5: Vcc 3.3V and 5V works
*
*    Pins on Arduino: Pin 7 to Reset, rest as usual.
*
***************************************************************************************************/



//#include <HardwareSerial.h>
#include <NewSoftSerial.h>


// configurate

const int uDrive_AcknoledgementTimeout = 1000; // Time (in ms) to wait for ack


// const

#define ACK 0x06  // Acknoledge byte (means ok)
#define NACK 0x15 // Negative acknoledge byte (means not ok)
#define EXTENDED 0x40
#define INITIALIZE_MODULE 0x55
#define INITIALIZE_DISK 0x69
#define READ_FILE 0x61
#define WRITE_FILE 0x74





// var
int uDrive_resetPin;

//HardwareSerial* uDrive_serial = &Serial1; // Hardware Serial
NewSoftSerial uDrive_serial(10, 11);
boolean uDrive_isIntitialized = false;
boolean uDrive_isDiskloaded = false;
boolean uDrive_errors = false;





// SETUP


// Initialize uDrive

boolean uDrive_Initialize (long baud, int resetPin) {

        // Reset device
        uDrive_resetPin = resetPin;
        pinMode (uDrive_resetPin, OUTPUT);
        digitalWrite (uDrive_resetPin, LOW);
        delay (10);
        digitalWrite (uDrive_resetPin, HIGH);
        delay (1000);
        // Setup device
        uDrive_serial.begin(baud);
        uDrive_serial.print (INITIALIZE_MODULE, BYTE);
        if (!private_uDrive_CheckAcknoledgement (5000))
        return false;


        // return
        uDrive_isIntitialized = true;
        return true;

}


// Load Disk

boolean uDrive_LoadDisk () {

        uDrive_serial.print (EXTENDED, BYTE);
        uDrive_serial.print (INITIALIZE_DISK, BYTE);
        // check if successful
        if (!private_uDrive_CheckAcknoledgement ())
        return false;


        // return
        uDrive_isDiskloaded = true;
        return true;

}









// FUNCTIONS




// Write File
boolean uDrive_writeFile (char* Filename, int Value) {

        char str[] = "/0/0/0/0/0/0/0/0/0/0";
        int len = uDrive_Parse (Value, str);
        return uDrive_writeFile (Filename, str, len, true);

}
boolean uDrive_writeFile (char* Filename, long Value) {

        char str[] = "/0/0/0/0/0/0/0/0/0/0";
        int len = uDrive_Parse (Value, str);
        return uDrive_writeFile (Filename, str, len, true);

}
boolean uDrive_writeFile (char* Filename, char* Data) {

        int size = uDrive_LengthOfString(Data);
        return uDrive_writeFile (Filename, Data, size, true);

}
boolean uDrive_writeFile (char* Filename, char* Data, long Filesize, boolean appendMode) {

        // check
        if (Data == NULL) // check if array was passed
        return false;
        if (!uDrive_isIntitialized || !uDrive_isDiskloaded) // check if device was initialized
        return false;


        // Filename
        int filenamelength = uDrive_LengthOfString (Filename, 12);
        if (filenamelength == -1) { // return if filename has more then 12 characters (including .txt or whatever)
                uDrive_errors = true;
                return false;
        }
        // Options
        int handshaking = 0; // handshaking: values 0 to 50 (32hex)
        if (Filesize > 50)
        handshaking = 50;
        byte options = 0x00;
        options += handshaking; // handshaking
        if (appendMode) // appendmode: 0x00 (no append) or 0x80 (append)
        options += 0x80;


        // Write Header
        uDrive_serial.print (EXTENDED, BYTE);
        uDrive_serial.print (WRITE_FILE, BYTE);
        uDrive_serial.print (options, BYTE); // Options
        for (int i=0;i<filenamelength;i++) // Filename
        uDrive_serial.print (Filename[i], BYTE);
        uDrive_serial.print (0x00, BYTE);
        uDrive_serial.print (((Filesize >> 24) & 0xff), BYTE); // Filesize
        uDrive_serial.print (((Filesize >> 16) & 0xff), BYTE);
        uDrive_serial.print (((Filesize >> 8) & 0xff), BYTE);
        uDrive_serial.print (((Filesize) & 0xff), BYTE);


        // OK?
        if (!private_uDrive_CheckAcknoledgement (5000)) {
                uDrive_errors = true;
                return false;
        }


        // Write Data (Filesize bytes)
        int loops = 1;
        if (handshaking > 0)
        loops = ceil((float)Filesize / (float)handshaking);
        int tmpFilesize = handshaking;
        if (tmpFilesize == 0)
        tmpFilesize = Filesize;
        for (int iBlocks=0;iBlocks<loops;iBlocks++) {

                if (iBlocks+1 == loops)
                tmpFilesize = Filesize - (iBlocks*handshaking);
                // Write Block
                for (int i=0; i<tmpFilesize;i++)
                uDrive_serial.print(Data[i + iBlocks * handshaking]);
                // OK?
                if (!private_uDrive_CheckAcknoledgement (5000)) {
                        uDrive_errors = true;
                        return false;
                }

        }


        // return
        return true;

}










// PRIVATE FUNCTIONS


// Check response if OK

boolean private_uDrive_CheckAcknoledgement () {

        private_uDrive_CheckAcknoledgement (uDrive_AcknoledgementTimeout);

}
boolean private_uDrive_CheckAcknoledgement (int timeout) {

        // wait for ack
        int iDelay = 10;
        int i = 0;
        while (!uDrive_serial.available()) {

                delay (iDelay);
                if (timeout / iDelay < i)
                return false;
                i += 1;

        }


        // Read
        int val = uDrive_serial.read();


        // Return
        if (val != ACK)
        return false;
        return true;

}


















// ADDITIONAL FUNCTIONS



// uDrive_LengthOfString
//    return -1 if no end character was found

int uDrive_LengthOfString (char* data) {

        return uDrive_LengthOfString (data, 32000);

}
int uDrive_LengthOfString (char* data, int abort) {

        int count = 0;
        char *p = data;
        abort += 1;
        while (*p != '\0') {
                p++;
                count++;
                if (count == abort)
                return -1;
        }
        return count;

}


// uDrive_Parse
//   returns the length of the string

int uDrive_Parse (int Value, char* ret) {

        return uDrive_Parse ((long) Value, ret);

}
int uDrive_Parse (long Value, char* ret) {

        int base = 10;
        int buf[8 * sizeof(long)]; // Assumes 8-bit chars.
        int i = 0;
        if (Value == 0) {
                *ret++ = '0';
                return 1;
        }
        while (Value > 0) {
                buf[i++] = Value % base;
                Value /= base;
        }
        int len = i;
        for (i--; i >= 0; i--){
                *ret++ =  (char)(buf[i] < 10 ? '0' + buf[i] : 'A' + buf[i] - 10);
        }
        *ret++ = 0;
        return len;

}




// uDrive_HasErrors

boolean uDrive_HasErrors () {

        boolean tmp = uDrive_errors;
        uDrive_errors = false;
        return tmp;

}