#define SCPI_CORE_STATE__PROCESSDATA_GUARD #include "app/scpi/CommandParserStates/process_data.h" #define SCPI_CORE_PRIVATE // access to 'scpi_core_private.h' #define SCPI_PROCESS_DATA_C // access to some const variables in 'scpi_core_private.h' #include "app/scpi/scpi_core_private.h" #include "app/scpi/scpi_args.h" // Refer to: // [1] IEEE 488.2 Standard, revision IEEE Std 488.2-1987 (1992) // "IEEE Standard Codes, Formats, Protocols, and Common Commands for Use With IEEE Std 488.1-1987, IEEE // Standard Digital Interface for Programmable Instrumentation" // [2] SCPI Specification, revision 1999.0 // "Standard Commands for Programmable Instruments (SCPI), VERSION 1999.0, May 1999" // ================================================================================= // @sProcessProgramDataContext_t // State's private context typedef typedef struct { // @seqCommandProcessor // The SCPI command functor-sequencer xFSeqObj_t seqCommandProcessor; // @commonCtx // Common context for all command handlers sProcessProgramDataCommonContext_t commonCtx; } sProcessProgramDataContext_t; // ================================================================================= // @ctx_ProcessProgramData // State's private context sProcessProgramDataContext_t ctx_ProcessProgramData; // ================================================================================= // @fsqvbl_ProcessProgramData // State's virtual table static void fsqe_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx ); static void fsql_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx ); static const struct fFSeqEntry_t * fsqf_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx, const struct fFSeqEntry_t * * pDeferredNext ); const fFSeqVTable_t fsqvbl_ProcessProgramData = { .f = fsqf_ProcessProgramData, .enter = fsqe_ProcessProgramData, .leave = fsql_ProcessProgramData }; // ================================================================================= // @fsqe_ProcessProgramData // State's enter routine static void fsqe_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx ) { sScpiParserContext_t * global_ctx = ctx; sProcessProgramDataContext_t * private_ctx = fsq_GetPrivateCtxRef(this); assert ( eScpiEventWrite == global_ctx->sEvent.eCode || eScpiEventRead == global_ctx->sEvent.eCode ); // pass through the global context reference private_ctx->commonCtx.global_ctx = global_ctx; // pass through the handler context reference private_ctx->commonCtx.handler_ctx = global_ctx->sParser.xHandler->ctx; // forward set status-indicator private_ctx->commonCtx.status = eProgramDataSyntaxError; // reset arguments count private_ctx->commonCtx.args = 0; private_ctx->commonCtx.needParseEndOfCommand = true; // forward set private_ctx->commonCtx.argsParserStatus = eScpiStatus_invalid; // forward set private_ctx->commonCtx.event = eProgramData_Event_Write; // forward event code for handler // construct state machine object, pass through the global context reference fSeqConstruct( &private_ctx->seqCommandProcessor, &global_ctx->sParser.xHandler->entry, &private_ctx->commonCtx ); // startup the SCPI parser state machine fSeqStartup( &private_ctx->seqCommandProcessor, NULL ); // check if the command handler already set a correct parser // iterator to the end of current command: if not, it is required // search for end of the command to search the next in the line. if( private_ctx->commonCtx.needParseEndOfCommand ) { parseArguments_helper( &private_ctx->commonCtx, NULL, 0, 0 ); } } // ================================================================================= // @fsql_ProcessProgramData // State's leave routine static void fsql_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx ) { sScpiParserContext_t * global_ctx = ctx; sProcessProgramDataContext_t * private_ctx = fsq_GetPrivateCtxRef(this); // shutdown the SCPI parser state machine fSeqShutdown( &private_ctx->seqCommandProcessor ); (void)global_ctx; } // ================================================================================= // @fsqf_ProcessProgramData // State's body routine // // Note: processes 'read' and 'write' events for SCPI command data payload // static const struct fFSeqEntry_t * fsqf_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx, const struct fFSeqEntry_t * * pnext ) { const struct fFSeqEntry_t * nextstate = NULL; sScpiParserContext_t * global_ctx = ctx; sProcessProgramDataContext_t * private_ctx = fsq_GetPrivateCtxRef(this); // Process event: switch( global_ctx->sEvent.eCode ) { case eScpiEventRestart: { // go to reset state nextstate = fsq_GetStateById(this,eParserResetState); goto L_fsqf_ProcessProgramData_EXIT; } break; case eScpiEventError: // Transport error. { // Enter error state nextstate = fsq_GetStateById(this,eHandleError); // enter the error state goto L_fsqf_ProcessProgramData_EXIT; } break; case eScpiEventWrite: // this dispatch() is called directly or after @eProcessCommandHeader my_assert( private_ctx->commonCtx.event == eProgramData_Event_Write ); case eScpiEventRead: // this dispatch() is called directly or after @eProcessCommandHeader { // Place response message separator // See [1], 8.4.1 // if( global_ctx->sMessage.bLeadResponseSep ) { // By design the @bLeadResponseSep is set only after the previous // executed command in the chain. If there no free room in the output // buffer, the command handler can not place it's own response. // But if there free room in the buffer the first element is placed must // be response message unit separator. if( global_ctx->sRead.nBufferSize > 0 ) { // Place response unit separator before calling command handler scpi_WriteCharOutput( SCPI_CHARACTER_RESPONSE_SEPARATOR ); global_ctx->sMessage.bLeadResponseSep = false; // reset indicator } else { // keep indicator for the next iteration } } } break; default: // invalid event code { // Enter error state nextstate = fsq_GetStateById(this,eHandleError); // enter the error state goto L_fsqf_ProcessProgramData_EXIT; } } assert( eProgramData_Event_Done != private_ctx->commonCtx.event ); private_ctx->commonCtx.status = eProgramDataSyntaxError; // forward set private_ctx->commonCtx.isQuery = global_ctx->sMessage.bQuery; private_ctx->commonCtx.argErrIdx = SCPI_MAX_ARGS; // reset erroneous argument index to default // process the internal state machine state // Note: @private_ctx->commonCtx.event is firstly prepared in @fsqe_ProcessProgramData fSeqDispatch( &private_ctx->seqCommandProcessor ); switch( private_ctx->commonCtx.status ) { case eProgramDataNeedRead: // Check for: output-buffer filled { // set global status: 'success' global_ctx->sEvent.eStatus = eScpiStatus_success; // need to continue reading, it is not done yet. nextstate = NULL; // stay in current state // Check if OUTPUT buffer is available (eScpiEventRead): switch( global_ctx->sEvent.eCode ) { // Output buffer is available case eScpiEventRead: { // "6.1.10.2.1 Message Available Message (MAV)", [1] scpi_UpdateMessageAvailable(); // Check actual handler event code: switch( private_ctx->commonCtx.event ) { // Handler just have processed 'Write' event case eProgramData_Event_Write: { // Handler returns NEED READ during WRITE event // Change event code for handler private_ctx->commonCtx.event = eProgramData_Event_Read; // And call this state again now to provide 'Reading' for the handler nextstate = this; } break; // Handler just have processed 'Read' event case eProgramData_Event_Read: { // Handler returns NEED READ during READ event // in case EOM (end-of-messsage) indicator has been reset by handler if( ! global_ctx->sMessage.r_bEndOfMessage ) { // return 'eScpiStatus_need_data' status instead of 'eScpiStatus_success' // This provides another call to read the rest part of data global_ctx->sEvent.eStatus = eScpiStatus_need_data; } } break; default: assert(false); // impossible code } } break; // No output buffer is available case eScpiEventWrite: { // "6.1.10.2.1 Message Available Message (MAV)", [1] // .sRead structure is not initialized, but the handler respond that // is there data to send. // Force Set MAV bit // "11.5.2.1 Function", [1] // "<...> MAV becomes TRUE whenever data is ready and not necessarily in the Output Queue; <...>" GPIBMachine.fGPIB_set_message_available_bit( &global_ctx->sGPIB.registers, true ); // Handler status is 'eProgramDataNeedRead', so it is required to change next event code. // Change event code for handler private_ctx->commonCtx.event = eProgramData_Event_Read; } break; } } break; case eProgramDataNeedData: // Check for: need data { // more data required // do not leave the state, keep @eScpiStatus_need_data status global_ctx->sEvent.eStatus = eScpiStatus_need_data; (void)private_ctx->commonCtx.event; // keep event code unchagned } break; case eProgramDataDone: // Check for: done-indicator { // Command completely done. // Check event code switch( global_ctx->sEvent.eCode ) { // "6.1.10.2.1 Message Available Message (MAV)", [1] // .sRead structure is not initialized in *some* cases case eScpiEventWrite: case eScpiEventRead: { global_ctx->sMessage.r_bEndOfMessage = true; // assert read-event end-of-message indicator // "6.1.10.2.1 Message Available Message (MAV)", [1] scpi_UpdateMessageAvailable(); } break; } // Fix: USBTMC does not allow empty transfer (transfer size equal to 0) // So, it is requried to send at least one byte if no response is available // after success code is returned. if( eScpiEventRead == global_ctx->sEvent.eCode ) if( 0 == global_ctx->sRead.nTotalLength ) // transfer length if( 0 < global_ctx->sRead.nBufferSize ) { scpi_WriteCharOutput( ' ' ); } private_ctx->commonCtx.event = eProgramData_Event_Done; // formal reset event code to 'Done' // set success status global_ctx->sEvent.eStatus = eScpiStatus_success; // Search for the following message unit separator // goto next state: eProcessEndOfProgramData nextstate = fsq_GetStateById(this,eProcessEndOfProgramData); } break; default: // error cases { // Specify the argument token to the last parsed argument token const sStrToken_t * pErroneousArgumentToken = &global_ctx->sParser.xArgToken; // Check if the erroneous argument index is set if( private_ctx->commonCtx.argErrIdx < SCPI_MAX_ARGS ) // Check if index is in range of parsed arguments count if( private_ctx->commonCtx.argErrIdx < private_ctx->commonCtx.args ) { // Handler has specified the particularly parsed token as erroneous pErroneousArgumentToken = &private_ctx->commonCtx.argTokens[ private_ctx->commonCtx.argErrIdx ]; } switch( private_ctx->commonCtx.status ) { case eProgramDataRuntimeError: // Check for: run-time error { // Generate error message: common execution error // "11.5.1.1.5 Bit 4 N Execution ERROR (E)", [2] // "<...> the device shall continue parsing the input stream. <...>" fsq_RaiseError( SCPI_ERROR_EXECUTION_ERROR, SCPI_ERROR_EXECUTION_ERROR_MSG, global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail ); // Note: Execution error shall not cause the parser stop operate, // so, the error handler must process error code and pass the control to the // 'endof_process_data' state to continue processing next command. } break; case eProgramDataSyntaxError: // Check for: syntax error { // Generate error message fsq_RaiseError( SCPI_ERROR_SYNTAX_ERROR, SCPI_ERROR_SYNTAX_ERROR_MSG, global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail ); } break; case eProgramDataArgumentSyntax: // Check for: argument syntax error { // Command handler reports about argument parsing error // The specific error is already prepared in parser context // Retrieve error details: int32_t error; const char * pErrorMsg = getParserError( &(global_ctx->sParser.xCtxObj), &error ); if( error != SCPI_ERROR_SUCCESS ) { const void * shead; const void * stail; getParsedEntityDetails( &(global_ctx->sParser.xCtxObj), &shead, &stail, NULL ); // Generate error message fsq_RaiseErrorEx( error, pErrorMsg, shead, stail, // character global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command ); } } break; case eProgramDataIllegalArgument: // Check for: illegal argument value { // Command handler reports about illegal argument value // Generate error message fsq_RaiseErrorEx( SCPI_ERROR_ILLEGAL_PARAM, SCPI_ERROR_ILLEGAL_PARAM_MSG, pErroneousArgumentToken->shead, pErroneousArgumentToken->stail, // character global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command ); } break; case eProgramDataArgumentType: // Check for: argument type error case eProgramDataArgumentValue: // Check for: argument value error { // Command handler reports about argument value/range error // The specific error must be generated here // Generate error message fsq_RaiseErrorEx( SCPI_ERROR_PARAMETER_ERROR, SCPI_ERROR_PARAMETER_ERROR_MSG, pErroneousArgumentToken->shead, pErroneousArgumentToken->stail, // character global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command ); } break; case eProgramDataArgumentRange: // Check for: argument value range error { // Command handler reports about argument range error // The specific error must be generated here // Generate error message fsq_RaiseErrorEx( SCPI_ERROR_DATA_RANGE, SCPI_ERROR_DATA_RANGE_MSG, pErroneousArgumentToken->shead, pErroneousArgumentToken->stail, // character global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command ); } break; case eProgramData_SpecificError: // Check for: specific error { (void)fsq_RaiseError; // specific error message already generated by handler } break; default: { // Generate error message (common error code) fsq_RaiseError( SCPI_ERROR_COMMAND_ERROR, SCPI_ERROR_COMMAND_ERROR_MSG, global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail ); } } // formal reset event code to 'Done' private_ctx->commonCtx.event = eProgramData_Event_Done; // set failed status: see @fsql_ProcessProgramData global_ctx->sEvent.eStatus = eScpiStatus_failed; // Error condition // goto next state: eHandleError nextstate = fsq_GetStateById(this,eHandleError); } break; } L_fsqf_ProcessProgramData_EXIT: return nextstate; }