#define USB_APPLICATION_USBTMC_C
#include "usb/usb_config.h"
#include "usb/usb_bridge.h"
#include "usbd_usbtmc.h"
#include "usbapp/usb_application_usbtmc.h"
#include "usbapp/usbtmclib/usbtmclib_basic.h"
#include "app/led/led.h"
#include "my_assert.h"
//----------------------------------------------------------------
// Refer to:
// [1] USBTMC Standard, rev. 1.0, 14/04/2003
//     "Universal Serial Bus Test and Measurement Class Specification (USBTMC)"
// [2] USBTMC-USB488 Standard, rev. 1.0, 14/04/2003
//     "Universal Serial Bus Test and Measurement Class, Subclass USB488 Specification (USBTMC-USB488)
// [3] IEEE 488.1 Standard, ANSI/IEEE Std 488.1-1987
//     "IEEE Standard Digital Interface, ANSI/IEEE Std 488.1-1987"
// [4] IEEE 488.2 Standard, revision IEEE Std 488.2-1987
//     "IEEE Standard Codes, Formats, Protocols, and Common Commands for Use With IEEE Std 488.1-1987, IEEE
//      Standard Digital Interface for Programmable Instrumentation"
// [5] SCPI Specification, revision 1999.0
//     "Standard Commands for Programmable Instruments (SCPI), VERSION 1999.0, May 1999"
//----------------------------------------------------------------
#if DEBUG_USBTMC_REQUESTS > 0 
#define MAX_DEBUG_LOG 10
volatile int gDebugLogIdx = 0;
volatile int gDebugLogRollover = 0;
static uint32_t gDebugLog[ MAX_DEBUG_LOG ] = {0};
void debug_log( uint8_t value, uint16_t value2 )
{
   if( gDebugLogIdx == 0 )
   {
       memset( gDebugLog, 0, sizeof(gDebugLog) );
   }

   if( gDebugLogIdx >= MAX_DEBUG_LOG )
   {
       memset( gDebugLog, 0, sizeof(gDebugLog) );
       gDebugLogIdx = 0;
       gDebugLogRollover++;
   }

   gDebugLog[ gDebugLogIdx++ ] = (((uint32_t)value2) << 16) | value;
}
#define DEBUG_LOG_STATE_INITABIN  0x01
#define DEBUG_LOG_STATE_CHKSTATUS_PEND  0x02
#define DEBUG_LOG_STATE_CHKSTATUS_OK  0x03
#define DEBUG_LOG_STATE_DATAIN  0x04
#endif
//----------------------------------------------------------------
#define INDICATOR_PULSE_REQUEST_SUPPORT (CONFIG_LEDS)   // enable INDICATOR_PULSE if CONFIG_LEDS is available
//----------------------------------------------------------------

static int8_t fUSBTMCProto_Init();
static int8_t fUSBTMCProto_DeInit();
static void   fUSBTMCProto_Reset();
static bool   fUSBTMCProto_Setup( const tUSBSetupPacket_t * pSetup, bool bFirstStage, bool success );
static size_t fUSBTMCProto_ControlRx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining );
static size_t fUSBTMCProto_ControlTx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining );

const sUSBAppEntry_Control_t usbapplication_USBTMC_control = {

     .fUsbInit     = fUSBTMCProto_Init,
     .fUsbDeInit   = fUSBTMCProto_DeInit,
     .fUsbSetup    = fUSBTMCProto_Setup,
     .fUsbCtlEpRx  = fUSBTMCProto_ControlRx,
     .fUsbCtlEpTx  = fUSBTMCProto_ControlTx,
     .fResetEvent  = fUSBTMCProto_Reset,
};

// USBTMC_datain_beginsend()
// Begin the transmission using DataIN handler virtual call.
// Due to the hardware does not support NAK-sent-interrupt, it is
// required to initiate the first packet sending to start TX-flow (DataIN).
// This function issues the first packet sending using virtual DataIN event.
uint8_t USBTMC_datain_beginsend( uint8_t epnum );

// USBTMC_datain_sendprepared()
// Begin the transmission using already prepared TX-transfer.
// Due to the hardware does not support NAK-sent-interrupt, it is
// required to initiate the first packet sending to start TX-flow (DataIN).
// This function issues the first packet sending using virtual DataIN event.
uint8_t USBTMC_datain_sendprepared( uint8_t epnum );

// USBTMC_datain_zerosend()
// Queue zero packet in BulkIN EP without calling TX-handler
uint8_t USBTMC_datain_zerosend( uint8_t epnum );

static bool fUSBTMCProto_DataRxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * rx );
static bool fUSBTMCProto_DataTxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * tx );
static bool fUSBTMCProto_DataErrHandler( uint8_t bEpLogAddress, uint32_t error );
static bool fUSBTMCProto_IntTxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * tx );
static bool fUSBTMCProto_IntErrHandler( uint8_t bEpLogAddress, uint32_t error );


// @usbapplication_USBTMC_dataapp
// Data channel handler descriptor
const sUSBAppEntry_Data_t usbapplication_USBTMC_dataapp = {

     .fDataInitHandler   = NULL,
     .fDataRxHandler     = fUSBTMCProto_DataRxHandler,
     .fDataTxHandler     = fUSBTMCProto_DataTxHandler,
     .fDataErrHandler    = fUSBTMCProto_DataErrHandler,
     .fDataDeInitHandler = NULL,
};

// @usbapplication_USBTMC_intapp
// Interrupt channel handler descriptor
const sUSBAppEntry_Data_t usbapplication_USBTMC_intapp = {

     .fDataInitHandler   = NULL,
     .fDataRxHandler     = NULL, // no data receiving is expected
     .fDataTxHandler     = fUSBTMCProto_IntTxHandler,
     .fDataErrHandler    = fUSBTMCProto_IntErrHandler,
     .fDataDeInitHandler = NULL,
};

//===========================================================================================
#if INDICATOR_PULSE_REQUEST_SUPPORT
// 4.2.1.8 GET_CAPABILITIES, table 37, [1]
// Refer to @sUSBTMC_GetCapabilities_Response_t
#define USBTMC_IFACE_CAPABILITIES    (USBTMC_IFACE_CAPABILITIES_INDICATOR_PULSE)  // Indicator Pulse request is supported :)
#else
// 4.2.1.8 GET_CAPABILITIES, table 37, [1]
// Refer to @sUSBTMC_GetCapabilities_Response_t
#define USBTMC_IFACE_CAPABILITIES    (0) // No Indicator Pulse request supported :(
#endif

// 4.2.1.8 GET_CAPABILITIES, table 37, [1]
// Refer to @sUSBTMC_GetCapabilities_Response_t
#define USBTMC_DEVICE_CAPABILITIES   (0)

// 4.2.2 GET_CAPABILITIES, table 8, [2]
// Refer to @sUSB488_GetCapabilities_Response_t
#define USB488_IFACE_CAPABILITIES    (USB488_IFACE_CAPABILITIES_4882IF)

// 4.2.2 GET_CAPABILITIES, table 8, [2]
// Refer to @sUSB488_GetCapabilities_Response_t
#define USB488_DEVICE_CAPABILITIES  (USB488_DEVICE_CAPABILITIES_SERVICE_REQUEST | USB488_DEVICE_CAPABILITIES_SCPI)

// ===================================================================================
// @sUsbTmcContext
// Local module context variables
static struct {

//sUSBTransfer_t * xBulkOutTransfer;              // data buffer for Bulk-Out transfer
//sUSBTransfer_t * xBulkInTransfer;               // data buffer for Bulk-In transfer
  const USBD_DescriptorsTypeDef * xDescHandlers;  // class GetDescriptor handlers set

  bool  bInitiateBulkOutAbort;                    // Initiate BulkOut abort request flag: true if received, false otherwise
  bool  bInitiateBulkInAbort;                     // Initiate BulkIn abort request flag: true if received, false otherwise
  bool  bInitiateClear;                           // Initiate Clear request flag: true if received, false otherwise

  struct
  {
      union
      {
         uint8_t rawBytesResponse[1];              // byte-array access to avoid casting and warnings
         sUSBTMC_InitiateAbortBulkInOut_Response_t responseInitiateAbortBulkX;
         sUSBTMC_CheckAbortBulkOut_Response_t      responseCheckAbortBulkOut;
         sUSBTMC_CheckAbortBulkIn_Response_t       responseCheckAbortBulkIn;
         sUSBTMC_InitiateClear_Response_t          responseInitiateClear;
         sUSBTMC_CheckClear_Response_t             responseCheckClear;
         sUSBTMC_GetCapabilities_Response_t        responseGetCapabilities;
         sUSBTMC_IndicatorPulse_Response_t         responseIndicatorPulse;
         sUSBTMC_USB488_ReadStatusByte_Response_t  responseUsb488ReadStatusByte;
      };
  }
  tmpSetup;  // temporary variables are valid during single request: Setup -> Rx/Tx

} sUsbTmcContext;
// ===================================================================================
static int8_t fUSBTMCProto_Init()
{
    // Check the Control-OUT buffer size
    // If the buffer size is less than the maximum packet size to be sent:
    if( usbd_usbtmc_get_control_tx_transfer_size() < sizeof( sUsbTmcContext.tmpSetup ) )
    {
        // Error: the buffer size is less than the maximum packet size to be sent.
        // See @fUSBTMCProto_ControlTx for details.
        // The function @fUSBTMCProto_ControlTx does not support multi-packet mode
        return -1; // error:
    }

    // Register Bulk-Data handler -> @usbapplication_USBTMC_dataapp
    usb_bridge_register_dataapp( USBTMC_BULKIO_EP, &usbapplication_USBTMC_dataapp );

    // Register Interrupt-Data handler -> @usbapplication_USBTMC_intapp
    usb_bridge_register_dataapp( USBTMC_INTIO_EP, &usbapplication_USBTMC_intapp );

    sUsbTmcContext.bInitiateBulkOutAbort = false;
    sUsbTmcContext.bInitiateBulkInAbort = false;
    sUsbTmcContext.bInitiateClear = false;
  //sUsbTmcContext.xBulkOutTransfer = ;
  //sUsbTmcContext.xBulkInTransfer = USBD_USBTMC_GetDataTxTransferHandle();
    sUsbTmcContext.xDescHandlers = USBD_USBTMC_GetDescriptorHandlers();

    if( tmclib_status_success != tmclib_init(
             USBD_USBTMC_GetDataRxTransferHandle(),
             USBD_USBTMC_GetDataTxTransferHandle(),
             USBD_USBTMC_GetInterruptTxTransferHandle() )
      )
    {
        return -1; // error
    }

    return 0;
}
// ===================================================================================
static int8_t fUSBTMCProto_DeInit()
{
    usb_bridge_unregister_dataapp( USBTMC_BULKIO_EP );
    usb_bridge_unregister_dataapp( USBTMC_INTIO_EP );

    tmclib_deinit();

    return 0;
}
// ===================================================================================
// @fUSBTMCProto_Reset
// USB Bus reset event
static void fUSBTMCProto_Reset()
{
    tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkout(0, true) );
    tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkin(0, true) );
    tmclib_generic_event( COMPOUND_tmclib_event_context_abort_interruptin(0, true) );

    sUsbTmcContext.bInitiateBulkOutAbort = false;
    sUsbTmcContext.bInitiateBulkInAbort = false;
    sUsbTmcContext.bInitiateClear = false;
}

// ===================================================================================
// @fUSBTMCProto_Setup
// USBTMC protocol, USB Setup Handler
// Processes the class requests and reacts to some standard requests
static bool fUSBTMCProto_Setup( const tUSBSetupPacket_t * pSetup, bool bFirstStage, bool success )
{
    // Check the request type
    switch( pSetup->bmRequest & USB_REQ_TYPE_MASK )
    {
        // Standard request: this call is performed BEFORE the ENDPOINT clearing by core
        case USB_REQ_TYPE_STANDARD:
        {
            // Check request ID:
            switch( pSetup->bRequest )
            {
                // USBTMC must process CLEAR_FEATURE standard request with type ENDPOINT_HALT:
                // see section 4.1 "Standard requests", [1]
                case USB_REQ_CLEAR_FEATURE:
                {
                     // process ENDPOINT_HALT request type:
                     if( USB_FEATURE_EP_HALT == pSetup->wValue )
                     {
                         // Host tries to restore synchronization: clear stalled endpoint
                         // check the endpoint address:
                         switch( pSetup->wIndex )
                         {
                            case USBTMC_BULKOUT_EP:
                                 // section 4.1.1.1 "USBTMC interface Bulk-OUT endpoints", [1]
                                 tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkout(0, true) ); // forced interrupt current transfer

                                 // Clear EP and reset the buffer, restart receiving
                                 USBD_USBTMC_ClearEP( USBTMC_BULKOUT_EP );
                                 return true; // pass this request, core does the job
                            break;
                            case USBTMC_BULKIN_EP:
                                 // section 4.1.1.2 "USBTMC interface Bulk-IN endpoints", [1]
                                 tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkin(0, true) );  // forced interrupt current transfer

                                 // Clear EP and reset the buffer
                                 USBD_USBTMC_NakEP( USBTMC_BULKIN_EP ); // set NAK instead of VALID
                                 return true; // pass this request, core does the job
                            break;
                            case USBTMC_INTERRUPTIN_EP:
                                 // [1] has no information about such event
                                 // Do processing in formal way: abort the transfer and set NAK state over EP
                                 tmclib_generic_event( COMPOUND_tmclib_event_context_abort_interruptin(0, true) );  // forced interrupt current transfer

                                 // Clear EP and reset the buffer
                                 USBD_USBTMC_NakEP( USBTMC_INTERRUPTIN_EP ); // set NAK instead of VALID
                                 return true; // pass this request, core does the job
                            break;
                         }
                     }
                }
                break;
            }
        }
        break;

        // see 4.2.1 "USBTMC requests", [1]
        case USB_REQ_TYPE_CLASS:
        {

            // In accordance with USBTMC standard [1], the code "STATUS_SUCCESS" means
            // the device queued the INITIATE request to be performed.
            // @bInitiateBulkInAbort must remember if such request is queued in case
            // the host will try to queue another request. If host performs another
            // request, the device must discard the response for previously queued request
            // and queue new request only if the previous request is already done.
            // But if the device executes this request synchroniusly, there no previously
            // request pending, and the request is always done.
            // In other words, @bInitiateBulkInAbort shows only if is there the response
            // prepared for previously INITIATE request or not for next CHECK_STATUS request.

            // 4.2.1.1 "USBTMC split transactions", point 3, [1]
            if( sUsbTmcContext.bInitiateBulkOutAbort && 
                (eUSBTMC_INITIATE_ABORT_BULK_OUT == pSetup->bRequest ||
                 eUSBTMC_INITIATE_CLEAR == pSetup->bRequest ) )
            {
                // reset the prepared response: comes down to reseting the flag
                sUsbTmcContext.bInitiateBulkOutAbort = false;
            }
            // ---
            // 4.2.1.1 "USBTMC split transactions", point 3, [1]
            if( sUsbTmcContext.bInitiateBulkInAbort && 
                (eUSBTMC_INITIATE_ABORT_BULK_IN == pSetup->bRequest ||
                 eUSBTMC_INITIATE_CLEAR == pSetup->bRequest ) )
            {
                // reset the prepared response: comes down to reseting the flag
                sUsbTmcContext.bInitiateBulkInAbort = false;
            }
            // ---
            // 4.2.1.1 "USBTMC split transactions", point 3, [1]
            if( sUsbTmcContext.bInitiateClear && (eUSBTMC_CHECK_CLEAR_STATUS != pSetup->bRequest) )
            {
                // reset the prepared response: comes down to reseting the flag
                sUsbTmcContext.bInitiateClear = false;
            }
            // ---

            // Check request ID:
            switch( pSetup->bRequest )
            {
                // Aborts a Bulk-OUT transfer.
                // see 4.2.1.2 "INITIATE_ABORT_BULK_OUT", [1]
                case eUSBTMC_INITIATE_ABORT_BULK_OUT:
                {
                     if( pSetup->wIndex != USBTMC_BULKOUT_EP )
                       return false; // Error, 4.2.1.2 INITIATE_ABORT_BULK_OUT, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX) );

                     // preserve the bTag to put the value into the response (fUSBTMCProto_ControlRx)
                     tmclib_bulkout_gettag_latest( &sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX.bTag );

                     // try to abort BulkOut transfer with the specified bTag
                     if( tmclib_status_success == tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkout((uint8_t)pSetup->wValue, false) ) )
                     {
                         // Halt the BulkOut endpoint to interrupt the transfer, 4.2.1.2, table 18 [1]
                         USBD_USBTMC_StallEP( USBTMC_BULKOUT_EP );

                         sUsbTmcContext.bInitiateBulkOutAbort = true;  // INITIATE response prepared
                         sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX.USBTMC_status = eUSBTMC_STATUS_SUCCESS;
                     }
                     else
                     {
                         // error: transfer isn't in progress
                         sUsbTmcContext.bInitiateBulkOutAbort = false;  // INITIATE response not prepared
                         sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX.USBTMC_status = eUSBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS;
                     }

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                }
                break;

                // Returns the status of the previously sent INITIATE_ABORT_BULK_OUT request.
                case eUSBTMC_CHECK_ABORT_BULK_OUT_STATUS:
                {
                     if( pSetup->wIndex != USBTMC_BULKOUT_EP )
                       return false; // Error, 4.2.1.3 CHECK_ABORT_BULK_OUT_STATUS, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseCheckAbortBulkOut) );

                     if( sUsbTmcContext.bInitiateBulkOutAbort )
                     {
                         uint32_t NBYTES_RXD = 0;

                         // retireve number of received bytes during last transfer
                         tmclib_bulkout_getcounter( &NBYTES_RXD );

                         sUsbTmcContext.tmpSetup.responseCheckAbortBulkOut.NBYTES_RXD = NBYTES_RXD;

                         sUsbTmcContext.tmpSetup.responseCheckAbortBulkOut.USBTMC_status = eUSBTMC_STATUS_SUCCESS;  // successfully aborted

                         // clear the request indicator
                         sUsbTmcContext.bInitiateBulkOutAbort = false;
                     }
                     else
                     {
                         sUsbTmcContext.tmpSetup.responseCheckAbortBulkOut.USBTMC_status = eUSBTMC_STATUS_SPLIT_NOT_IN_PROGRESS; // not INITIATE request received
                     }

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                }
                break;

                // Aborts a Bulk-IN transfer.
                case eUSBTMC_INITIATE_ABORT_BULK_IN:
                {
                     if( pSetup->wIndex != USBTMC_BULKIN_EP )
                       return false; // Error, 4.2.1.4 INITIATE_ABORT_BULK_IN, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX) );

                     // preserve the bTag to put the value into the response (fUSBTMCProto_ControlTx)
                     tmclib_bulkin_gettag_latest( &sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX.bTag );

                     // try to abort BulkIn transfer with the specified bTag
                     if( tmclib_status_success == tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkin((uint8_t)pSetup->wValue, false) ) )
                     {
                         // Restore endpoint, BulkIN EP must not be halted, due to:
                         // - 4.2.1.4, table 26;
                         // - 3.3.2.4 "Halt", table 12
                         USBD_USBTMC_NakEP( USBTMC_BULKIN_EP );

                         // [1], 4.2.1.4 INITIATE_ABORT_BULK_IN, Table 26
                         // "If a short packet has not been queued, queue a shortpacket to terminate the transfer."
                         // "If a short packet can not yet be queued, wait until a short packet can be queued."
                         USBTMC_datain_zerosend( USBTMC_BULKIN_EP ); // queue a short packet to terminate transfer
                         #if DEBUG_USBTMC_REQUESTS > 0
                         debug_log( DEBUG_LOG_STATE_INITABIN, 0 );
                         #endif
                         sUsbTmcContext.bInitiateBulkInAbort = true;  // INITIATE response prepared
                         sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX.USBTMC_status = eUSBTMC_STATUS_SUCCESS;
                     }
                     else
                     {
                         // error: transfer isn't in progress
                         sUsbTmcContext.bInitiateBulkInAbort = false; // INITIATE response not prepared
                         sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX.USBTMC_status = eUSBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS;
                     }

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                }
                break;

                // Returns the status of the previously sent INITIATE_ABORT_BULK_IN request.
                case eUSBTMC_CHECK_ABORT_BULK_IN_STATUS:
                {
                     if( pSetup->wIndex != USBTMC_BULKIN_EP )
                       return false; // Error, 4.2.1.5 CHECK_ABORT_BULK_IN_STATUS, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn) );

                     if( sUsbTmcContext.bInitiateBulkInAbort )
                     {
                         uint32_t NBYTES_TXD = 0;

                         // retireve number of transferred bytes during last transfer
                         tmclib_bulkin_getcounter( &NBYTES_TXD );

                         if( ! USBD_USBTMC_GetTxQueuedSize( USBTMC_BULKIN_EP, NULL ) )
                         {
                             // [1]. 4.2.1.5 CHECK_ABORT_BULK_IN_STATUS, Table 29 -- CHECK_ABORT_BULK_IN_STATUS USBTMC_status values
                             // The device must set NBYTES_TXD to the appropriate value.
                             sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn.NBYTES_TXD = NBYTES_TXD;

                             // BulkIN transfer already reset, FIFO is empty, so bmAbortBulkIn.D0 is zero.
                             sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn.bmAbortBulkIn = 0;
                             sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn.USBTMC_status = eUSBTMC_STATUS_SUCCESS;  // successfully aborted
                             #if DEBUG_USBTMC_REQUESTS > 0
                             debug_log( DEBUG_LOG_STATE_CHKSTATUS_OK, 0 );
                             #endif
                             // clear the request status
                             sUsbTmcContext.bInitiateBulkInAbort = false;
                         }
                         else
                         {
                             // [1]. 4.2.1.5 CHECK_ABORT_BULK_IN_STATUS, Table 29 -- CHECK_ABORT_BULK_IN_STATUS USBTMC_status values
                             // The device must set NBYTES_TXD = 0x00000000.
                             sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn.NBYTES_TXD = 0;

                             // BulkIN transfer already reset, but FIFO is not empty and short packet must be send, so bmAbortBulkIn.D0 is set.
                             sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn.bmAbortBulkIn = 1;
                             sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn.USBTMC_status = eUSBTMC_STATUS_PENDING;  // successfully aborted
                             #if DEBUG_USBTMC_REQUESTS > 0
                             debug_log( DEBUG_LOG_STATE_CHKSTATUS_PEND, 0 );
                             #endif
                             // keep the request status true
                             sUsbTmcContext.bInitiateBulkInAbort = true;
                         }
                     }
                     else
                     {
                         sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn.USBTMC_status = eUSBTMC_STATUS_SPLIT_NOT_IN_PROGRESS; // not INITIATE request received
                     }

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                }
                break;

                // Clears all previously sent pending and
                // unprocessed Bulk-OUT USBTMC message
                // content and clears all pending Bulk-IN transfers
                // from the USBTMC interface.
                case eUSBTMC_INITIATE_CLEAR:
                {
                     //#error sUsbTmcContext.xDescHandler
                     if( pSetup->wIndex != USB_TMC_INTERFACE_VALUE )
                       return false; // Error, see 4.2.1.6 INITIATE_CLEAR, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseInitiateClear) );

                     // 4.2.1.6 INITIATE_CLEAR, [2]
                     // Halt Bulk-OUT EP and clear FIFO
                     USBD_USBTMC_StallEP( USBTMC_BULKOUT_EP );
                     // Abort bulk-out transfer and clear the state
                     tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkout(0, true) );

                     // 4.2.1.6 INITIATE_CLEAR, [2]
                     // Remove all queued packets and clear FIFO for BulkIn
                     // Set NAK status to interrupt the transfer and clear buffer
                     USBD_USBTMC_NakEP( USBTMC_BULKIN_EP );
                     // Abort bulk-in transfer and clear the state
                     tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkin(0, true) );

                     // --------------- Non-standard -----------------------------------------------------
                     // There no recomendations for Interrupt-IN EP during INITIATE_CLEAR request
                     // But it is good to restore the initial state to clear Interrupt-IN EP here.

                     // Set NAK state for Interrupt-IN EP
                     USBD_USBTMC_NakEP( USBTMC_INTERRUPTIN_EP );
                     // abort interrupt-in transfer
                     tmclib_generic_event( COMPOUND_tmclib_event_context_abort_interruptin(0, true) );  
                     // ----------------------------------------------------------------------------------

                     // Clean up USBTMC and SCPI module
                     tmclib_generic_event( COMPOUND_tmclib_event_context_device_clear(false) ); 

                     sUsbTmcContext.tmpSetup.responseInitiateClear.USBTMC_status = eUSBTMC_STATUS_SUCCESS;  // successfully cleared

                     sUsbTmcContext.bInitiateClear = true;
#if DEBUG_USB > 0 // SCPI debug only
                     extern void spy_tx_log_cleanup();
                     spy_tx_log_cleanup();
#endif
                     return true; // pass this request -> fUSBTMCProto_ControlTx
                }
                break;

                // Returns the status of the previously sent INITIATE_CLEAR request.
                case eUSBTMC_CHECK_CLEAR_STATUS:
                {
                     if( pSetup->wIndex != USB_TMC_INTERFACE_VALUE )
                       return false; // Error, see 4.2.1.7 CHECK_CLEAR_STATUS, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseCheckClear) );

                     // [1], 4.2.1.7 CHECK_CLEAR_STATUS
                     if( sUsbTmcContext.bInitiateClear )
                     {
                         sUsbTmcContext.tmpSetup.responseCheckClear.bmClear = 0; // all FIFOs are cleared, bmClear.D0 is zero
                         sUsbTmcContext.tmpSetup.responseCheckClear.USBTMC_status = eUSBTMC_STATUS_SUCCESS;  // successfully aborted

                         // clear the request indicator
                         sUsbTmcContext.bInitiateClear = false;
                     }
                     else
                     {
                         sUsbTmcContext.tmpSetup.responseCheckClear.USBTMC_status = eUSBTMC_STATUS_SPLIT_NOT_IN_PROGRESS; // not INITIATE request received
                     }

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                }
                break;

                // Returns attributes and capabilities of the USBTMC interface.
                case eUSBTMC_GET_CAPABILITIES:
                {
                     if( pSetup->wIndex != USB_TMC_INTERFACE_VALUE )
                       return false; // Error, see 4.2.1.8 GET_CAPABILITIES, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseGetCapabilities) );

                     sUsbTmcContext.tmpSetup.responseGetCapabilities.USBTMC_status = eUSBTMC_STATUS_SUCCESS; // successfull
                     sUsbTmcContext.tmpSetup.responseGetCapabilities.bcdUSBTMC = USBTMC_VERSION;
                     sUsbTmcContext.tmpSetup.responseGetCapabilities.ifaceCaps = USBTMC_IFACE_CAPABILITIES;
                     sUsbTmcContext.tmpSetup.responseGetCapabilities.devCaps = USBTMC_DEVICE_CAPABILITIES;
                     sUsbTmcContext.tmpSetup.responseGetCapabilities.usb488.bcdUSB488 = USB488_VERSION;
                     sUsbTmcContext.tmpSetup.responseGetCapabilities.usb488.ifaceCaps = USB488_IFACE_CAPABILITIES;
                     sUsbTmcContext.tmpSetup.responseGetCapabilities.usb488.devCaps = USB488_DEVICE_CAPABILITIES;

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                }
                break;

                // A mechanism to turn on an activity indicator for
                // identification purposes. The device indicates
                // whether or not it supports this request in the
                // GET_CAPABILITIES response packet.
                case eUSBTMC_INDICATOR_PULSE:
                {
                     #if INDICATOR_PULSE_REQUEST_SUPPORT
                     if( pSetup->wIndex != USB_TMC_INTERFACE_VALUE )
                       return false; // Error, see 4.2.1.9 INDICATOR_PULSE, [1]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseIndicatorPulse) );

                     sUsbTmcContext.tmpSetup.responseIndicatorPulse.USBTMC_status = eUSBTMC_STATUS_SUCCESS; // successfull

                     // Perform LED Signaling
                     LEDHandle.SetMode( eLedMode_Signaling );

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                     #else
                     return false; // Error, see 4.2.1.9 INDICATOR_PULSE, [1]
                     #endif
                }
                break;
#warning SCPI Add "default:" and route to USB488 dependent request processor (4.3 USB488 subclass specific requests)
                // Allows to read the status byte using class request
                case eUSBTMC_USB488_READ_STATUS_BYTE:
                {                 
                     if( pSetup->wIndex != USB_TMC_INTERFACE_VALUE )
                       return false; // Error, see 4.3.1 READ_STATUS_BYTE, [2]

                     if( pSetup->wValue & 0x80 ||
                         (pSetup->wValue & 0x7F) < 2 )
                       return false; // Error, see 4.3.1 READ_STATUS_BYTE, [2]

                     // zero init the response bytes
                     memset( sUsbTmcContext.tmpSetup.rawBytesResponse, 0, sizeof(sUsbTmcContext.tmpSetup.responseUsb488ReadStatusByte) );

                     sUsbTmcContext.tmpSetup.responseUsb488ReadStatusByte.USBTMC_status = eUSBTMC_STATUS_SUCCESS; // successfull
                     sUsbTmcContext.tmpSetup.responseUsb488ReadStatusByte.bTag = pSetup->wValue & 0x7F;

                     // 4.3.1.2 Response format for USB488 interfaces with an Interrupt-IN endpoint, [2]
                     sUsbTmcContext.tmpSetup.responseUsb488ReadStatusByte.bStatusByte = 0; // forward set to constant

                     // Set NAK state for Interrupt-IN EP
                     USBD_USBTMC_NakEP( USBTMC_BULKIN_EP );

                     // Initiate Interrupt-In transfer
                     tmclib_generic_event( COMPOUND_tmclib_event_context_interruptin_start( eventCtx, NULL, sUsbTmcContext.tmpSetup.responseUsb488ReadStatusByte.bTag ) );

                     return true; // pass this request -> fUSBTMCProto_ControlTx
                     
                }
                break;
            }
        }
        break;
    }

    return false;
}

// ===================================================================================
// fUSBTMCProto_ControlRx()
// The setup packet has been processed and filtered already in @fUSBTMCProto_Setup()
// This function receives the SETUP packet DATA payload
static size_t fUSBTMCProto_ControlRx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining )
{
    // No incoming packets expected via CONTROL-transfer
    return 0;
}
// ===================================================================================
// fUSBTMCProto_ControlTx()
// The setup packet has been processed and filtered already in @fUSBTMCProto_Setup()
// This function queues the response only.
static size_t fUSBTMCProto_ControlTx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining )
{
    // All the followed requests require the response.
    // To simplify the implementation this function requires
    // the control endpoint MAX_PACKET_SIZE to be not less than
    // the higest incoming packet.
    // This function does not support multi-packet mode, the buffer size
    // must have enough space to place the biggest possible response.
    // see @fUSBTMCProto_Init for details.

    size_t bytes = 0;

    if( 0 != idx )
      return 0; // multi-packet transaction: wtf? see the comment above

    // Check the request type
    switch( pSetup->bmRequest & USB_REQ_TYPE_MASK )
    {
        // see 4.2.1 "USBTMC requests", [1]
        case USB_REQ_TYPE_CLASS:
        {
            // Check request ID:
            switch( pSetup->bRequest )
            {
                case eUSBTMC_INITIATE_ABORT_BULK_OUT: // Aborts a Bulk-OUT transfer.
                case eUSBTMC_INITIATE_ABORT_BULK_IN:  // Aborts a Bulk-IN transfer.
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX) == bytesRemaining ) // 4.2.1.2, Table 19, [1]
                     {
                         bytes += usb_write_transfer( tx, sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseInitiateAbortBulkX) );
                     }
                }
                break;

                // Returns the status of the previously sent INITIATE_ABORT_BULK_OUT request.
                case eUSBTMC_CHECK_ABORT_BULK_OUT_STATUS:
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseCheckAbortBulkOut) == bytesRemaining ) // 4.2.1.3, Table 22, [1]
                     {
                         bytes += usb_write_transfer( tx, &sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseCheckAbortBulkOut) );
                     }
                }
                break;

                // Returns the status of the previously sent INITIATE_ABORT_BULK_IN request.
                case eUSBTMC_CHECK_ABORT_BULK_IN_STATUS:
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn) == bytesRemaining ) // 4.2.1.5, Table 28, [1]
                     {
                         bytes += usb_write_transfer( tx, &sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseCheckAbortBulkIn) );
                     }
                }
                break;

                // Clears all previously sent pending and
                // unprocessed Bulk-OUT USBTMC message
                // content and clears all pending Bulk-IN transfers
                // from the USBTMC interface.
                case eUSBTMC_INITIATE_CLEAR:
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseInitiateClear) == bytesRemaining ) // 4.2.1.6, Table 30, [1]
                     {
                         bytes += usb_write_transfer( tx, sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseInitiateClear) );
                     }
                }
                break;

                // Returns the status of the previously sent INITIATE_CLEAR request.
                case eUSBTMC_CHECK_CLEAR_STATUS:
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseCheckClear) == bytesRemaining ) // 4.2.1.7, Table 34, [1]
                     {
                         bytes += usb_write_transfer( tx, sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseCheckClear) );
                     }
                }
                break;

                // Returns attributes and capabilities of the USBTMC interface.
                case eUSBTMC_GET_CAPABILITIES:
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseGetCapabilities) == bytesRemaining ) // 4.2.1.8 GET_CAPABILITIES, table 37, [1]
                     {
                         bytes += usb_write_transfer( tx, sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseGetCapabilities) );
                     }
                }
                break;

                // A mechanism to turn on an activity indicator for
                // identification purposes. The device indicates
                // whether or not it supports this request in the
                // GET_CAPABILITIES response packet.
                case eUSBTMC_INDICATOR_PULSE:
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseIndicatorPulse) == bytesRemaining ) // 4.2.1.9 INDICATOR_PULSE, table 39, [1]
                     {
                         bytes += usb_write_transfer( tx, sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseIndicatorPulse) );
                     }
                }
                break;

                // Allows to read the status byte using class request
                case eUSBTMC_USB488_READ_STATUS_BYTE:
                {
                     if( sizeof(sUsbTmcContext.tmpSetup.responseUsb488ReadStatusByte) == bytesRemaining ) // 4.3.1 READ_STATUS_BYTE, [2]
                     {
                         bytes += usb_write_transfer( tx, sUsbTmcContext.tmpSetup.rawBytesResponse, sizeof(sUsbTmcContext.tmpSetup.responseUsb488ReadStatusByte) );

                         // need to force-call Interrupt-IN handler (fUSBTMCProto_IntTxHandler();)
                         USBTMC_datain_beginsend( USBTMC_INTERRUPTIN_EP );
                     }
                }
                break;
            }
        }
        break;
    }

    return bytes;
}
// ===================================================================================
// @fUSBTMCProto_DataRxHandler
// USBTMC Data Incoming handler (Bulk-Out)
static bool fUSBTMCProto_DataRxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * rx )
{
   eTMCLibStatus_t status;

bulk_out_label:

   status = tmclib_generic_event( COMPOUND_tmclib_event_context_bulkout( eventCtx, rx ) );

   if( USBTMC_NOTERROR(status) )
   {
       if( USBTMC_WARNING(status) )
       {
           switch( status )
           {
              case tmclib_status_invalid_param:
              {
                  my_assert( status != tmclib_status_invalid_param ); // 'tmclib_bulkout_event' SHALL never return 'tmclib_status_invalid_param' 
              }
              break;

              case tmclib_status_in_progress: 
              {
                  my_assert( status != tmclib_status_in_progress ); // 'tmclib_bulkout_event' never returns 'tmclib_status_in_progress' 
              }
              break;

              case tmclib_status_again:
              {
                  // there is some data in @rx transfer required to be processed
              }
              goto bulk_out_label;;

              case tmclib_status_need_data:
              {
                  my_assert( status != tmclib_status_need_data );   // 'tmclib_bulkout_event' never returns 'tmclib_status_need_data' 
              }
              break;

              case tmclib_status_not_in_progress:
              {
                  my_assert( status != tmclib_status_not_in_progress );  // 'tmclib_bulkout_event' never returns 'tmclib_status_not_in_progress'
              }
              break;

              case tmclib_status_no_more_data:
              {
                  my_assert( status != tmclib_status_no_more_data );     // 'tmclib_bulkout_event' never returns 'tmclib_status_no_more_data'
              }
              break;

              case tmclib_status_need_read:
              {
                  // normal operation
                  uint8_t rc = USBTMC_datain_sendprepared( USBTMC_BULKIN_EP );

                  (void)rc;
              }
              break;

              default:
                  my_assert( status == tmclib_status_success );     // 'tmclib_bulkout_event' returns undefined code 
           }
       }
   }
   else
   {
       if( status == tmclib_status_halt_bulkin )
       {
           // 3.3.2.4 Halt, [1]
           USBD_USBTMC_StallEP( USBTMC_BULKIN_EP );
       }

       // 3.2.2.3 Bulk-OUT transfer protocol errors, [1]
       // 3.2.2.4 Halt, [1]
       USBD_USBTMC_StallEP( USBTMC_BULKOUT_EP );
   }

   return USBTMC_NOTERROR(status);
}
// ===================================================================================
// @fUSBTMCProto_DataTxHandler
// USBTMC Data Outcoming handler (Bulk-IN)
// Note: User shall reset Short-Packet indicator using @usb_transfer_modify_flags
// ... routine to avoid sending short packet in case no data queued into the transfer
// ... after successful call (return true with empty transfer)
static bool fUSBTMCProto_DataTxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * tx )
{   
   eTMCLibStatus_t status = tmclib_generic_event( COMPOUND_tmclib_event_context_bulkin( eventCtx, tx ) );

   if( USBTMC_NOTERROR(status) )
   {   
       if( USBTMC_WARNING(status) )
       {
           switch( status )
           {
              case tmclib_status_invalid_param:
              {
                  my_assert( status != tmclib_status_invalid_param ); // 'tmclib_bulkin_event' SHALL never return 'tmclib_status_invalid_param' 
              }
              break;

              case tmclib_status_in_progress: 
              {
                  my_assert( status != tmclib_status_in_progress ); // 'tmclib_bulkin_event' never returns 'tmclib_status_in_progress' 
              }
              break;

              case tmclib_status_again:
              {
                  my_assert( status != tmclib_status_again );       // 'tmclib_bulkin_event' never returns 'tmclib_status_again' 
              }
              break;

              case tmclib_status_need_data:
              {
                  my_assert( status != tmclib_status_need_data );   // 'tmclib_bulkin_event' never returns 'tmclib_status_need_data' 
              }
              break;

              case tmclib_status_not_in_progress:
              case tmclib_status_no_more_data:  // need set NAK condition over EP
              {
                  // cleanup EP IN to provide NAK response in next IN transaction
                  USBD_USBTMC_NakEP( USBTMC_BULKIN_EP );
              }
              break;

              case tmclib_status_need_read:
              {
                  // normal operation
              }
              break;

              default:
                  my_assert( status == tmclib_status_success );     // 'tmclib_bulkin_event' returns undefined code 
           }
       }

       return true;
   }

   return false;
}
// ===================================================================================
// @fUSBTMCProto_DataErrHandler
// USBTMC Error handler for Data channel
static bool fUSBTMCProto_DataErrHandler( uint8_t bEpLogAddress, uint32_t error )
{
   #warning BulkOut -> Stall
   return true; // bibb
}
// ===================================================================================
// @fUSBTMCProto_IntTxHandler
// USBTMC Interrupt Outcoming handler (Interrupt-IN)
static bool fUSBTMCProto_IntTxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * ntx )
{
   eTMCLibStatus_t status = tmclib_generic_event( COMPOUND_tmclib_event_context_interruptin( eventCtx, ntx ) );

   if( USBTMC_NOTERROR(status) )
   {   
       switch( status )
       {
          case tmclib_status_invalid_param:
          {
              my_assert( status != tmclib_status_invalid_param ); // 'tmclib_interruptin_event' SHALL never return 'tmclib_status_invalid_param' 
          }
          break;

          case tmclib_status_in_progress: 
          {
              my_assert( status != tmclib_status_in_progress ); // 'tmclib_interruptin_event' never returns 'tmclib_status_in_progress' 
          }
          break;

          case tmclib_status_again:
          {
              my_assert( status != tmclib_status_again );       // 'tmclib_interruptin_event' never returns 'tmclib_status_again' 
          }
          break;

          case tmclib_status_need_data:
          {
              my_assert( status != tmclib_status_need_data );   // 'tmclib_interruptin_event' never returns 'tmclib_status_need_data' 
          }
          break;

          case tmclib_status_not_in_progress:
          case tmclib_status_no_more_data:  // need set NAK condition over EP
          {
              // cleanup EP IN to provide NAK response in next IN transaction
              USBD_USBTMC_NakEP( USBTMC_INTERRUPTIN_EP );
          }
          break;

          case tmclib_status_success:
          case tmclib_status_need_read:
          {
              // normal operation
          }
          break;

          default:
              my_assert( status == tmclib_status_success );     // 'tmclib_interruptin_event' returns undefined code 
       }

       return true;
   }

   return false;
}
// ===================================================================================
// @fUSBTMCProto_IntErrHandler
// USBTMC Error handler for Interrupt channel
static bool fUSBTMCProto_IntErrHandler( uint8_t bEpLogAddress, uint32_t error )
{
   return true; // bibb
}
// ===================================================================================
// USBTMC_datain_beginsend()
// Begin the transmission using DataIN handler virtual call.
// Due to the hardware does not support NAK-sent-interrupt, it is
// required to initiate the first packet sending to start TX-flow (DataIN).
// This function issues the first packet sending using virtual DataIN event.
//
uint8_t USBTMC_datain_beginsend( uint8_t epnum )
{
    // issue virtual DataIN event:
    return USBD_USBTMC_DataIn_BeginSend( epnum, false );
}
// ===================================================================================
// USBTMC_datain_sendprepared()
// Begin the transmission using already prepared TX-transfer.
// Due to the hardware does not support NAK-sent-interrupt, it is
// required to initiate the first packet sending to start TX-flow (DataIN).
// This function issues the first packet sending using virtual DataIN event.
//
uint8_t USBTMC_datain_sendprepared( uint8_t epnum )
{
    // issue virtual DataIN event:
    return USBD_USBTMC_DataIn_BeginSend( epnum, true );
}
// ===================================================================================
// USBTMC_datain_zerosend()
// Queue zero packet in BulkIN EP without calling TX-handler
//
uint8_t USBTMC_datain_zerosend( uint8_t epnum )
{    
    return USBD_USBTMC_DataIn_ZeroSend( epnum );
}