/*******************************************************************************
  MPLAB Harmony Application Source File

  Company:
    Microchip Technology Inc.

  File Name:
    app_usbcdc.c

  Summary:
    This file contains the source code for the MPLAB Harmony application.

  Description:
    This file contains the source code for the MPLAB Harmony application.  It
    implements the logic of the application's state machine and it may call
    API routines of other MPLAB Harmony modules in the system, such as drivers,
    system services, and middleware.  However, it does not call any of the
    system interfaces (such as the "Initialize" and "Tasks" functions) of any of
    the modules in the system or make any assumptions about when those functions
    are called.  That is the responsibility of the configuration-specific system
    files.
 *******************************************************************************/

// *****************************************************************************
// *****************************************************************************
// Section: Included Files
// *****************************************************************************
// *****************************************************************************

#include "app_usbcdc.h"
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

// *****************************************************************************
// *****************************************************************************
// Section: Global Data Definitions
// *****************************************************************************
// *****************************************************************************

// *****************************************************************************
/* Application Data

  Summary:
    Holds application data

  Description:
    This structure holds the application's data.

  Remarks:
    This structure should be initialized by the APP_USBCDC_Initialize function.

    Application strings and buffers are be defined outside this structure.
*/

APP_USBCDC_DATA app_usbcdcData;

#define APP_READ_BUFFER_SIZE    512
#define APP_WRITE_BUFFER_SIZE   512
uint8_t CACHE_ALIGN cdcReadBuffer[APP_READ_BUFFER_SIZE];
uint8_t CACHE_ALIGN cdcWriteBuffer[APP_WRITE_BUFFER_SIZE];
char cdcGetBuffer[APP_READ_BUFFER_SIZE];
int cdcGetBufferLength = 0;
char cdcPrintfBuffer[APP_WRITE_BUFFER_SIZE];


// *****************************************************************************
// *****************************************************************************
// Section: Application Callback Functions
// *****************************************************************************
// *****************************************************************************

USB_DEVICE_CDC_EVENT_RESPONSE UsbCdcDeviceEventHandler
(
    USB_DEVICE_CDC_INDEX index,
    USB_DEVICE_CDC_EVENT event,
    void * pData,
    uintptr_t userData
)
{
    APP_USBCDC_DATA * app_usbcdcDataObject;
    USB_CDC_CONTROL_LINE_STATE * controlLineStateData;
    USB_DEVICE_CDC_EVENT_DATA_READ_COMPLETE * eventDataRead;

    app_usbcdcDataObject = (APP_USBCDC_DATA *)userData;

    switch(event)
    {
    case USB_DEVICE_CDC_EVENT_GET_LINE_CODING:
        /* This means the host wants to know the current line
         * coding. This is a control transfer request. Use the
         * USB_DEVICE_ControlSend() function to send the data to
         * host.  */
        USB_DEVICE_ControlSend(app_usbcdcDataObject->deviceHandle,
                &app_usbcdcDataObject->getLineCodingData, sizeof(USB_CDC_LINE_CODING));
        break;

    case USB_DEVICE_CDC_EVENT_SET_LINE_CODING:
        /* This means the host wants to set the line coding.
         * This is a control transfer request. Use the
         * USB_DEVICE_ControlReceive() function to receive the
         * data from the host */
        USB_DEVICE_ControlReceive(app_usbcdcDataObject->deviceHandle,
                &app_usbcdcDataObject->setLineCodingData, sizeof(USB_CDC_LINE_CODING));
        break;

    case USB_DEVICE_CDC_EVENT_SET_CONTROL_LINE_STATE:
        /* This means the host is setting the control line state.
         * Read the control line state. We will accept this request
         * for now. */
        controlLineStateData = (USB_CDC_CONTROL_LINE_STATE *)pData;
        app_usbcdcDataObject->controlLineStateData.dtr = controlLineStateData->dtr;
        app_usbcdcDataObject->controlLineStateData.carrier = controlLineStateData->carrier;

        USB_DEVICE_ControlStatus(app_usbcdcDataObject->deviceHandle, USB_DEVICE_CONTROL_STATUS_OK);
        break;

    case USB_DEVICE_CDC_EVENT_SEND_BREAK:
        /* This means that the host is requesting that a break of the
         * specified duration be sent. Read the break duration */
        app_usbcdcDataObject->breakData = ((USB_DEVICE_CDC_EVENT_DATA_SEND_BREAK *)pData)->breakDuration;

        /* Complete the control transfer by sending a ZLP  */
        USB_DEVICE_ControlStatus(app_usbcdcDataObject->deviceHandle, USB_DEVICE_CONTROL_STATUS_OK);
        break;

    case USB_DEVICE_CDC_EVENT_READ_COMPLETE:
        /* This means that the host has sent some data*/
        eventDataRead = (USB_DEVICE_CDC_EVENT_DATA_READ_COMPLETE *)pData;
        app_usbcdcDataObject->isReadComplete = true;
        app_usbcdcDataObject->numBytesRead = eventDataRead->length;
        break;

    case USB_DEVICE_CDC_EVENT_CONTROL_TRANSFER_DATA_RECEIVED:
        /* The data stage of the last control transfer is
         * complete. For now we accept all the data */
        USB_DEVICE_ControlStatus(app_usbcdcDataObject->deviceHandle, USB_DEVICE_CONTROL_STATUS_OK);
        break;

    case USB_DEVICE_CDC_EVENT_CONTROL_TRANSFER_DATA_SENT:
        /* This means the GET LINE CODING function data is valid. We don't
         * do much with this data in this demo. */
        break;

    case USB_DEVICE_CDC_EVENT_WRITE_COMPLETE:
        /* This means that the data write got completed. We can schedule
         * the next read. */
        app_usbcdcDataObject->isWriteComplete = true;
        break;

    default:
        break;
    }

    return USB_DEVICE_CDC_EVENT_RESPONSE_NONE;
}

void UsbDeviceEventHandler
(
    USB_DEVICE_EVENT event,
    void * eventData,
    uintptr_t context
)
{
    USB_DEVICE_EVENT_DATA_CONFIGURED *configuredEventData;

    switch(event)
    {
    case USB_DEVICE_EVENT_SOF:
        /* This event is used for switch debounce. This flag is reset
         * by the switch process routine. */
        app_usbcdcData.sofEventHasOccurred = true;
        break;

    case USB_DEVICE_EVENT_RESET:
        app_usbcdcData.isConfigured = false;
        break;

    case USB_DEVICE_EVENT_CONFIGURED:
        /* Check the configuration. We only support configuration 1 */
        configuredEventData = (USB_DEVICE_EVENT_DATA_CONFIGURED*)eventData;

        if ( configuredEventData->configurationValue == 1 )
        {
            /* Register the CDC Device application event handler here.
             * Note how the app_usbcdcData object pointer is passed as the
             * user data */

            USB_DEVICE_CDC_EventHandlerSet(USB_DEVICE_CDC_INDEX_0, UsbCdcDeviceEventHandler, (uintptr_t)&app_usbcdcData);

            /* Mark that the device is now configured */
            app_usbcdcData.isConfigured = true;
        }
        break;

    case USB_DEVICE_EVENT_POWER_DETECTED:
        /* VBUS was detected. We can attach the device */
        USB_DEVICE_Attach(app_usbcdcData.deviceHandle);
        break;

    case USB_DEVICE_EVENT_POWER_REMOVED:
        /* VBUS is not available any more. Detach the device. */
        USB_DEVICE_Detach(app_usbcdcData.deviceHandle);
        break;

    case USB_DEVICE_EVENT_SUSPENDED:
    case USB_DEVICE_EVENT_RESUMED:
    case USB_DEVICE_EVENT_ERROR:
    default:
        break;
    }
}

// *****************************************************************************
// *****************************************************************************
// Section: Application Local Functions
// *****************************************************************************
// *****************************************************************************
void APP_USBCDC_Printf( const char *format, ... )
{
    size_t len = 0;
    size_t padding = 0;
    va_list args = {0};

    va_start( args, format );

    len = vsnprintf( cdcPrintfBuffer, APP_WRITE_BUFFER_SIZE, format, args );

    va_end( args );

    if( len > 0 && len < APP_WRITE_BUFFER_SIZE )
    {
        cdcPrintfBuffer[len] = '\0';

        if( len + app_usbcdcData.numBytesWrite >= APP_WRITE_BUFFER_SIZE )
        {
            app_usbcdcData.numBytesWrite = 0;
        }

        strcpy( (char*)&app_usbcdcData.cdcWriteBuffer[app_usbcdcData.numBytesWrite], cdcPrintfBuffer );

        padding = len % 4;

        if( padding > 0 )
        {
            padding = 4 - padding;
        }

        app_usbcdcData.numBytesWrite += len + padding;
    }

}

uint32_t APP_USBCDC_ReadBytes( char *ReadBytes, int ReadLength )
{
    if( cdcGetBufferLength>0 )
    {
        if( cdcGetBufferLength < ReadLength )
            ReadLength = cdcGetBufferLength;
        cdcGetBufferLength = 0;
        memset( ReadBytes, 0, ReadLength );
        memcpy( ReadBytes, cdcGetBuffer, ReadLength );

        return ReadLength;
    }
    return 0;
}

/*****************************************************
 * This function is called in every step of the
 * application state machine.
 *****************************************************/
bool APP_USBCDC_StateReset(void)
{
    // This function returns true if the device was reset

    bool retVal;

    if( app_usbcdcData.isConfigured == false )
    {
        app_usbcdcData.state = APP_USBCDC_STATE_WAIT_FOR_CONFIGURATION;
        app_usbcdcData.readTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
        app_usbcdcData.writeTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
        app_usbcdcData.isReadComplete = true;
        app_usbcdcData.isWriteComplete = true;
        app_usbcdcData.numBytesWrite = 0;
        retVal = true;
    }
    else
    {
        retVal = false;
    }

    return(retVal);
}

// *****************************************************************************
// *****************************************************************************
// Section: Application Initialization and State Machine Functions
// *****************************************************************************
// *****************************************************************************

/*******************************************************************************
  Function:
    void APP_USBCDC_Initialize ( void )

  Remarks:
    See prototype in app_usbcdc.h.
 */

void APP_USBCDC_Initialize ( void )
{
    /* Place the App state machine in its initial state. */
    app_usbcdcData.state = APP_USBCDC_STATE_INIT;

    /* Device Layer Handle  */
    app_usbcdcData.deviceHandle = USB_DEVICE_HANDLE_INVALID ;

    /* Device configured status */
    app_usbcdcData.isConfigured = false;

    /* Initial get line coding state */
    app_usbcdcData.getLineCodingData.dwDTERate = 115200;
    app_usbcdcData.getLineCodingData.bParityType = 0;
    app_usbcdcData.getLineCodingData.bDataBits = 8;

    /* Read Transfer Handle */
    app_usbcdcData.readTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;

    /* Write Transfer Handle */
    app_usbcdcData.writeTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;

    /* Initialize the read complete flag */
    app_usbcdcData.isReadComplete = true;

    /*Initialize the write complete flag*/
    app_usbcdcData.isWriteComplete = true;

    /* Reset other flags */
    app_usbcdcData.sofEventHasOccurred = false;

    /* Set up the read buffer */
    app_usbcdcData.cdcReadBuffer = &cdcReadBuffer[0];

    /* Set up the write buffer */
    app_usbcdcData.cdcWriteBuffer = &cdcWriteBuffer[0];

    /* Flush the write buffer */
    app_usbcdcData.numBytesWrite = 0;
}


/******************************************************************************
  Function:
    void APP_USBCDC_Tasks ( void )

  Remarks:
    See prototype in app_usbcdc.h.
 */

void APP_USBCDC_Tasks ( void )
{
    /* Check the application's current state. */
    switch ( app_usbcdcData.state )
    {
    /* Application's initial state. */
    case APP_USBCDC_STATE_INIT:
        // Open the device layer
        app_usbcdcData.deviceHandle = USB_DEVICE_Open( USB_DEVICE_INDEX_0, DRV_IO_INTENT_READWRITE );

        if( app_usbcdcData.deviceHandle != USB_DEVICE_HANDLE_INVALID )
        {
            // Register a callback with device layer to get event notification (for end point 0)
            USB_DEVICE_EventHandlerSet( app_usbcdcData.deviceHandle, UsbDeviceEventHandler, 0 );

            app_usbcdcData.state = APP_USBCDC_STATE_WAIT_FOR_CONFIGURATION;
        }
        else
        {
            // The Device Layer is not ready to be opened. We should try again later.
        }
        break;

    case APP_USBCDC_STATE_WAIT_FOR_CONFIGURATION:
        // Check if the device was configured
        if( app_usbcdcData.isConfigured )
        {
            // If the device is configured then lets start reading
            app_usbcdcData.state = APP_USBCDC_STATE_SCHEDULE_READ;
        }
        break;

    case APP_USBCDC_STATE_SCHEDULE_READ:
        if( APP_USBCDC_StateReset() )
        {
            break;
        }

        if( app_usbcdcData.isReadComplete == true )
        {
            app_usbcdcData.isReadComplete = false;

            memset( cdcGetBuffer, 0 , APP_READ_BUFFER_SIZE );
            cdcGetBufferLength = app_usbcdcData.numBytesRead;
            memcpy( cdcGetBuffer, app_usbcdcData.cdcReadBuffer, cdcGetBufferLength );

            app_usbcdcData.readTransferHandle =  USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
            USB_DEVICE_CDC_Read( USB_DEVICE_CDC_INDEX_0,
                    &app_usbcdcData.readTransferHandle, app_usbcdcData.cdcReadBuffer,
                    APP_READ_BUFFER_SIZE );

            if( app_usbcdcData.readTransferHandle == USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID )
            {
                app_usbcdcData.state = APP_USBCDC_STATE_ERROR;
                break;
            }
        }

        app_usbcdcData.state = APP_USBCDC_STATE_SCHEDULE_WRITE;
        break;

    case APP_USBCDC_STATE_SCHEDULE_WRITE:
        if( APP_USBCDC_StateReset() )
        {
            break;
        }

        if( app_usbcdcData.isWriteComplete == true &&
            app_usbcdcData.numBytesWrite > 0 )
        {
            app_usbcdcData.isWriteComplete = false;

            app_usbcdcData.writeTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
            USB_DEVICE_CDC_Write( USB_DEVICE_CDC_INDEX_0,
                    &app_usbcdcData.writeTransferHandle,
                    app_usbcdcData.cdcWriteBuffer, app_usbcdcData.numBytesWrite,
                    USB_DEVICE_CDC_TRANSFER_FLAGS_DATA_COMPLETE );

            // Flush write buffer
            app_usbcdcData.numBytesWrite = 0;

            if( app_usbcdcData.writeTransferHandle == USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID )
            {
                app_usbcdcData.state = APP_USBCDC_STATE_ERROR;
                break;
            }
        }

        app_usbcdcData.state = APP_USBCDC_STATE_SCHEDULE_READ;
        break;

    /* The default state should never be executed. */
    case APP_USBCDC_STATE_ERROR:
        /* TODO: Handle error in application's state machine. */
        break;
    }
}


/*******************************************************************************
 End of File
 */


