j123b567 / scpi-parser

Open Source SCPI device library
BSD 2-Clause "Simplified" License
459 stars 192 forks source link

Working with ARB #80

Closed karakozov closed 7 years ago

karakozov commented 8 years ago

Hello, dear community! Thank you for the open project! I try to apply scpi parser library for my custom device. I have two channels on device. And I need to send data on any device channel and read data from any device channel using SCPI protocol. I add a two custom callback-functions SOURCE_Arb(scpi_t * context), SOURCE_ArbQ(scpi_t * context) and two patterns (see below) in file scpi-def.c and use examples/test-tcp and PuTTY for testing. I describe my experiments and hope for the help. Thank you!

1) When I send data to device, all work fine. SCPI parser give me a pointer on data block and data block size. See listing below:

On client side (putty): source3:arb #14ABCD

On server side (remote tcp-server):

SOURCE_Arb()
Channel: 3
Data: 0xb4133b
Size: 4
DATA DUMP:
 0: 41 42 43 44

2) But when I try to get data from device I can't get parameters from incoming request. Or I don't understand how to prepare correct request from client.

On client side (putty): source3:arb? #14

On server side (remote tcp-server): Response available only after 3 - 4 seconds. I suppose that parser wait four bytes data from client, it's necessary send four bytes of any data?

SOURCE_ArbQ()
Channel: 4

But how I can get how much data user want to get from device channel? Here my additional code. I can provide any additional information.

static scpi_result_t SOURCE_Arb(scpi_t * context)
{
    const char * data;
    size_t len;
    int32_t numbers[1];

    fprintf(stderr, "%s()\r\n", __func__);

    SCPI_CommandNumbers(context, numbers, 1, 1);

    fprintf(stderr, "Channel: %d\r\n", numbers[0]);

    if (SCPI_ParamArbitraryBlock(context, &data, &len, FALSE)) {
        fprintf(stderr, "Data: %p\r\n", data);
        fprintf(stderr, "Size: %d\r\n", len);

        show_dump(data, len);
    }

    return SCPI_RES_OK;
}

static scpi_result_t SOURCE_ArbQ(scpi_t * context)
{
    const char * data;
    size_t len;
    int32_t numbers[1];

    fprintf(stderr, "%s()\r\n", __func__);

    SCPI_CommandNumbers(context, numbers, 1, 1);

    fprintf(stderr, "Channel: %d\r\n", numbers[0]);

    if (SCPI_ParamArbitraryBlock(context, &data, &len, FALSE)) {
        fprintf(stderr, "Data: %p\r\n", data);
        fprintf(stderr, "Size: %d\r\n", len);
    }

    return SCPI_RES_OK;
}

const scpi_command_t scpi_commands[] = {

    /* IEEE Mandated Commands (SCPI std V1999.0 4.1.1) */
    { .pattern = "*CLS", .callback = SCPI_CoreCls,},
    {.pattern = "SOURce#:ARBitrary", .callback = SOURCE_Arb,},
    {.pattern = "SOURce#:ARBitrary?", .callback = SOURCE_ArbQ,},

    SCPI_CMD_LIST_END
};
cdwork commented 8 years ago

Hello, Vladimir! In project I'm using read/write for external FRAM.

1)For write into FRAM: Command on client side: "SYSTem:FRAM:DATA #H100, #14Test" where: #H100 - Offset for write in FRAM (is mandatory); #14Test - Arbitrary block (is mandatory). Length = 4 bytes. Raw data = 'Test'.

and code for parse:

scpi_result_t SCPI_system_fram(scpi_t * context){
    int32_t offset,length;

    // Parse mandatory offset
    if(!SCPI_ParamInt(context,&offset,true)){
        return SCPI_RES_ERR;
    }

    if(offset<0){
        SCPI_ErrorPush(context,SCPI_ERROR_ILLEGAL_PARAMETER_VALUE);
        return SCPI_RES_ERR;
    }

    void * data;
    // Parse mandatory arbitrary block and receive length of block and pointer to data
    if(!SCPI_ParamArbitraryBlock(context,&data,&length,true)){
        return SCPI_RES_ERR;
    }

    // Write to FRAM
    fram_write(offset,length,data);

    return SCPI_RES_OK;
}

2) For read from FRAM: Command on client side: "SYSTem:FRAM:DATA? #H100, 4" where: #H100 - Offset for read from FRAM (is mandatory); 4 - Length for read from FRAM.

Server return: "#14Test"

and code for parse:

#define DATA_FRAM_BLOCK_SIZE        32  // Read buffer length
scpi_result_t SCPI_system_framQ(scpi_t * context){
    int32_t offset,length;

    // Parse mandatory offset
    if(!SCPI_ParamInt(context,&offset,true)){
        return SCPI_RES_ERR;
    }

    // Parse mandatory length
    if(!SCPI_ParamInt(context,&length,true)){
        return SCPI_RES_ERR;
    }

    // Checking the correct parameters
    if(offset<0 || length<0){
        SCPI_ErrorPush(context,SCPI_ERROR_ILLEGAL_PARAMETER_VALUE);
        return SCPI_RES_ERR;
    }

    // Read buffer
    char data[DATA_FRAM_BLOCK_SIZE];

    // Return header of arbitrary block (i.e. "#14")
    SCPI_ResultArbitraryBlockHeader(context,length);

    int32_t chunk_len;
    while(length){
        // Limit chunk of read if needed
        if(length>DATA_FRAM_BLOCK_SIZE){
            chunk_len=DATA_FRAM_BLOCK_SIZE;
        }else{
            chunk_len=length;
        }

        //Read from FRAM
        fr_read(offset,chunk_len,data);

        // Return chunk...
        SCPI_ResultArbitraryBlockData(context,data,chunk_len);
        offset+=chunk_len;
        length-=chunk_len;

        // ... and read next chunk if read not complete
    }

    return SCPI_RES_OK;
}

I hope that the example will help.

karakozov commented 8 years ago

Hello, Dmitry! Thank you for your answer and code! I will change my project and test it later. Can you provide me, pattern that you setup in const scpi_command_t scpi_commands[] ? Thank you.

    {.pattern = "SOURce#:DATA", .callback = SOURCE_Data,},
    {.pattern = "SOURce#:DATA?", .callback = SOURCE_DataQ,},

I change application pattern and callback functions. Sample is working. I need a large data blocks (more than 1K) and set #define SCPI_INPUT_BUFFER_LENGTH 16384. Callback functions code:

static scpi_result_t SOURCE_Data(scpi_t * context)
{
    const char *data = 0;
    int32_t channel = 0;
    size_t length = 0;
    ssize_t written = 0;

    // Parse channel
    if(!SCPI_CommandNumbers(context, &channel, 1, 0)) {
        return SCPI_RES_ERR;
    }

    if((channel > 1) || (channel < 0)) {
        SCPI_ErrorPushEx(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE, " Invalid channel number.", 0);
        return SCPI_RES_ERR;
    }

    // Parse mandatory arbitrary block and receive length of block and pointer to data
    if(!SCPI_ParamArbitraryBlock(context, &data, &length, TRUE)){
        return SCPI_RES_ERR;
    }

    fprintf(stderr, "Channel: %d\r\n", channel);
    fprintf(stderr, "Data: %p\r\n", data);
    fprintf(stderr, "Size: %d\r\n", length);

    show_dump(data, length);

    // Checking the correct parameters
    if((length<16) || (length > 4096)) {
        SCPI_ErrorPushEx(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE, " Invalid data buffer length.", 0);
        return SCPI_RES_ERR;
    }

    if(!sport_configured[channel]) {
        SCPI_ErrorPushEx(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE, " Device channel not configured.", 0);
        return SCPI_RES_ERR;
    }

    while(length) {

        written = sport_write(channel, data, length);
        if(written < 0) {
            SCPI_ErrorPushEx(context, SCPI_ERROR_DEVICE_ERROR, strerror(written), 0);
            return SCPI_RES_ERR;
        }

        data += written;
        length -= written;
    }

    return SCPI_RES_OK;;
}

static scpi_result_t SOURCE_DataQ(scpi_t * context)
{
    int32_t channel = 0;
    int32_t length = 0;
    ssize_t readed = 0;
    char *data = 0;

    // Parse channel
    if(!SCPI_CommandNumbers(context, &channel, 1, 0)) {
        return SCPI_RES_ERR;
    }

    if((channel > 1) || (channel < 0)) {
        SCPI_ErrorPushEx(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE, " Invalid channel number.", 0);
        return SCPI_RES_ERR;
    }

    // Parse mandatory length
    if(!SCPI_ParamInt(context,&length,TRUE)) {
        return SCPI_RES_ERR;
    }

    // Checking the correct parameters
    if((length<16) || (length > 4096)) {
        SCPI_ErrorPushEx(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE, " Invalid data buffer length.", 0);
        return SCPI_RES_ERR;
    }

    if(!sport_configured[channel]) {
        SCPI_ErrorPushEx(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE, " Device channel not configured.", 0);
        return SCPI_RES_ERR;
    }

    data = malloc(length);
    if(!data) {
        SCPI_ErrorPushEx(context, SCPI_ERROR_DEVICE_ERROR, " Can't allocte memory for data buffer.", 0);
        return SCPI_RES_ERR;
    }

    // Return header of arbitrary block
    SCPI_ResultArbitraryBlockHeader(context, length);

    readed = sport_read(channel, data, length);
    if(readed < 0) {
            SCPI_ErrorPushEx(context, SCPI_ERROR_DEVICE_ERROR, strerror(readed), 0);
            return SCPI_RES_ERR;
    }

    // Return data block
    SCPI_ResultArbitraryBlockData(context, data, readed);

    free(data);

    return SCPI_RES_OK;
}
cdwork commented 7 years ago

Hello, Vladimir! I do through macro definition - I feel so comfortable, because the commands a lot. I'm adding a pattern and method name in LIST_OF_COMMANDS.

#define LIST_OF_COMMANDS                                                            \
    X("SYSTem:FRAM[:DATA]",                     SCPI_system_fram)                   \
    X("SYSTem:FRAM[:DATA]?",                    SCPI_system_framQ)                  \

And the macroses is already create the method declaration and add in scpi_commands[].

#define X(p,c) extern scpi_result_t (c)(scpi_t * context);
LIST_OF_COMMANDS
#undef X

static const scpi_command_t scpi_commands[] = {
    /* IEEE Mandated Commands (SCPI std V1999.0 4.1.1) */
    { .pattern = "*CLS", .callback = SCPI_CoreCls,},
    /*
    ...
    other predefined commands
    ...
    */

    #define X(p,c) {.pattern = p,       .callback = c},
    LIST_OF_COMMANDS
    #undef X

    SCPI_CMD_LIST_END
};

in the end, I get the following code:

extern scpi_result_t (SCPI_system_fram)(scpi_t * context);
extern scpi_result_t (SCPI_system_framQ)(scpi_t * context);

static const scpi_command_t scpi_commands[] = {
    /* IEEE Mandated Commands (SCPI std V1999.0 4.1.1) */
    { .pattern = "*CLS", .callback = SCPI_CoreCls,},
    /*
    ...
    other predefined commands
    ...
    */

    {.pattern = "SYSTem:FRAM[:DATA]",       .callback = SCPI_system_fram},
    {.pattern = "SYSTem:FRAM[:DATA]?",      .callback = SCPI_system_framQ},

    SCPI_CMD_LIST_END
};

DATA_FRAM_BLOCK_SIZE limits the size of the output data pieces required for one pass. This allows you to remove the restriction on the maximum size of the requested data. This is most relevant in microcontrollers, where the size of the available RAM is limited. For example, if DATA_FRAM_BLOCK_SIZE = 256 data output 4096 bytes will run for 16 cycles.

// Read buffer
char data[DATA_FRAM_BLOCK_SIZE];
// Return header of arbitrary block (i.e. "#14")
SCPI_ResultArbitraryBlockHeader(context,length);
int32_t chunk_len;
while(length){
    // Limit chunk of read if needed
    if(length>DATA_FRAM_BLOCK_SIZE){
        chunk_len=DATA_FRAM_BLOCK_SIZE;
    }else{
        chunk_len=length;
    }

    //Read from FRAM
    fr_read(offset,chunk_len,data);

    // Return chunk...
    SCPI_ResultArbitraryBlockData(context,data,chunk_len);
    offset+=chunk_len;
    length-=chunk_len;
    // ... and read next chunk if read not complete
}

At your code, I, frankly, do not understand the meaning of cycles while (length) { ... }

"sport_write" method can return a value other than "length"? If not, the loop is useless. Enough probably only verify that if a record was performed or not.

static scpi_result_t SOURCE_Data(scpi_t * context)
{
    ...
    while(length) {
        written = sport_write(channel, data, length);
        if(written < 0) {
            SCPI_ErrorPushEx(context, SCPI_ERROR_DEVICE_ERROR, strerror(written), 0);
            return SCPI_RES_ERR;
        }
        data += written;
        length -= written;
    }
    ...
}

The same goes for "sport_read".

static scpi_result_t SOURCE_DataQ(scpi_t * context)
{
    data = malloc(length);
    ...
    while(length) {
        readed = sport_read(channel, data, length);
        if(readed < 0) {
            SCPI_ErrorPushEx(context, SCPI_ERROR_DEVICE_ERROR, strerror(readed), 0);
            return SCPI_RES_ERR;
        }
        // Return data block
        SCPI_ResultArbitraryBlockData(context, data, readed);
        data += readed;
        length -= readed;
    }
    data = malloc(length);
    ...
}

Or "sport_read"/"sport_write" perform read/write only as much as can, and "length" is the limit of the maximum length?

karakozov commented 7 years ago

Hello, Dmitry!

Thank you for your detailed response and advices! I was modified my project and remove all while(length) cycles. I have embedded linux on my board and 32MB of RAM memory. The sport_read()/sport_write() read/write only length bytes. Dmitry, for using your macros I need to define my callbacks function before then macros was declared? Can you provide peace of your code without macros expansion?

As I understood SCPI is very flexible for customizing own command patterns. And I can use something like this patterns:

"SYSTem:SPORT#[:DATA]"
"SYSTem:SPORT#[:DATA]?"
"SYSTem:UART#[:DATA]"
"SYSTem:UART#[:DATA]?"

to get data from different device subsystem? It will be correctly or not with respect of SCPI standard?

Thank you a lot!

cdwork commented 7 years ago

Hello, Vladimir! Yes, SCPI is very flexible for customization own command patterns and you may adding a new commands "...if it conforms in style to commands in the subsystem where it is placed, and if there is not already a command to control the same functionality." (SCPI 1999.0:3.1 "Adding a Capability").

About macros. Declaration the callbacks functions before the macro is not required. This is a part of my file, with creating a scpi_commnands[] with more than 100 commands.

// "scpi_commands.h"
#ifndef SCPI_COMMANDS_H_
#define SCPI_COMMANDS_H_

#include "scpi/scpi.h"

#define LIST_OF_COMMANDS                                                            \
    X("SOURce[:CALibrator][:MODE]",                             SCPI_source_calibrator_mode)\
    X("SOURce[:CALibrator][:MODE]?",                            SCPI_source_calibrator_modeQ)\
    X("SOURce:FREQuency[:FIXed]",                               SCPI_source_frequency_fixed)\
    X("SOURce:FREQuency[:FIXed]?",                              SCPI_source_frequency_fixedQ)\
    X("SOURce[:CALibrator]:THD:FREQuency[:FILTred]:MODE",       SCPI_source_calibrator_thd_frequency_filtred_mode)\
    X("SOURce[:CALibrator]:THD:FREQuency[:FILTred]:MODE?",      SCPI_source_calibrator_thd_frequency_filtred_modeQ)\
    ...
    // More than 100 commands
    ...
    X("SYSTem:ERRor:CODE[:NEXT]?",              SCPI_SystemErrorCodeNextQ)\
    X("SYSTem:ERRor:ALL?",                      SCPI_SystemErrorAllQ)\
    X("SYSTem:ERRor:CODE:ALL?",                 SCPI_SystemErrorCodeAllQ)\

#define X(pat,cal) extern scpi_result_t (cal)(scpi_t * context);
LIST_OF_COMMANDS
#undef X

static const scpi_command_t scpi_commands[] = {
    /* IEEE Mandated Commands (SCPI std V1999.0 4.1.1) */
    { .pattern = "*CLS", .callback = SCPI_CoreCls,},
    { .pattern = "*ESE", .callback = SCPI_CoreEse,},
    { .pattern = "*ESE?", .callback = SCPI_CoreEseQ,},
    { .pattern = "*ESR?", .callback = SCPI_CoreEsrQ,},
    { .pattern = "*IDN?", .callback = SCPI_CoreIdnQ,},
    { .pattern = "*OPC", .callback = SCPI_CoreOpc,},
    { .pattern = "*OPC?", .callback = SCPI_CoreOpcQ,},
    { .pattern = "*RST", .callback = SCPI_CoreRst,},
    { .pattern = "*SRE", .callback = SCPI_CoreSre,},
    { .pattern = "*SRE?", .callback = SCPI_CoreSreQ,},
    { .pattern = "*STB?", .callback = SCPI_CoreStbQ,},
    { .pattern = "*TST?", .callback = SCPI_CoreTstQ,},
    { .pattern = "*WAI", .callback = SCPI_CoreWai,},

    /* Required SCPI commands (SCPI std V1999.0 4.2.1) */
    {.pattern = "SYSTem:ERRor[:NEXT]?", .callback = SCPI_SystemErrorNextQ,},
    {.pattern = "SYSTem:ERRor:COUNt?", .callback = SCPI_SystemErrorCountQ,},
    {.pattern = "SYSTem:VERSion?", .callback = SCPI_SystemVersionQ,},

    {.pattern = "STATus:QUEStionable[:EVENt]?", .callback = SCPI_StatusQuestionableEventQ,},
    {.pattern = "STATus:QUEStionable:ENABle", .callback = SCPI_StatusQuestionableEnable,},
    {.pattern = "STATus:QUEStionable:ENABle?", .callback = SCPI_StatusQuestionableEnableQ,},

    {.pattern = "STATus:PRESet", .callback = SCPI_StatusPreset,},

    #define X(pat,cal) {.pattern = pat,     .callback = cal},
    LIST_OF_COMMANDS
    #undef X

    SCPI_CMD_LIST_END
};

#endif /* SCPI_COMMANDS_H_ */

And so it would look like, if it is done without macro.

// "scpi_commands_without_macro.h"
#ifndef SCPI_COMMANDS_H_
#define SCPI_COMMANDS_H_

#include "scpi/scpi.h"

extern scpi_result_t SCPI_source_calibrator_mode(scpi_t * context);
extern scpi_result_t SCPI_source_calibrator_modeQ(scpi_t * context);
extern scpi_result_t SCPI_source_frequency_fixed(scpi_t * context);
extern scpi_result_t SCPI_source_frequency_fixedQ(scpi_t * context);
extern scpi_result_t SCPI_source_calibrator_thd_frequency_filtred_mode(scpi_t * context);
extern scpi_result_t SCPI_source_calibrator_thd_frequency_filtred_modeQ(scpi_t * context);
...
// More than 100 functions declarations
...
extern scpi_result_t SCPI_SystemErrorCodeNextQ(scpi_t * context);
extern scpi_result_t SCPI_SystemErrorAllQ(scpi_t * context);
extern scpi_result_t SCPI_SystemErrorCodeAllQ(scpi_t * context);

static const scpi_command_t scpi_commands[] = {
    /* IEEE Mandated Commands (SCPI std V1999.0 4.1.1) */
    { .pattern = "*CLS", .callback = SCPI_CoreCls,},
    { .pattern = "*ESE", .callback = SCPI_CoreEse,},
    { .pattern = "*ESE?", .callback = SCPI_CoreEseQ,},
    { .pattern = "*ESR?", .callback = SCPI_CoreEsrQ,},
    { .pattern = "*IDN?", .callback = SCPI_CoreIdnQ,},
    { .pattern = "*OPC", .callback = SCPI_CoreOpc,},
    { .pattern = "*OPC?", .callback = SCPI_CoreOpcQ,},
    { .pattern = "*RST", .callback = SCPI_CoreRst,},
    { .pattern = "*SRE", .callback = SCPI_CoreSre,},
    { .pattern = "*SRE?", .callback = SCPI_CoreSreQ,},
    { .pattern = "*STB?", .callback = SCPI_CoreStbQ,},
    { .pattern = "*TST?", .callback = SCPI_CoreTstQ,},
    { .pattern = "*WAI", .callback = SCPI_CoreWai,},

    /* Required SCPI commands (SCPI std V1999.0 4.2.1) */
    {.pattern = "SYSTem:ERRor[:NEXT]?", .callback = SCPI_SystemErrorNextQ,},
    {.pattern = "SYSTem:ERRor:COUNt?", .callback = SCPI_SystemErrorCountQ,},
    {.pattern = "SYSTem:VERSion?", .callback = SCPI_SystemVersionQ,},

    {.pattern = "STATus:QUEStionable[:EVENt]?", .callback = SCPI_StatusQuestionableEventQ,},
    {.pattern = "STATus:QUEStionable:ENABle", .callback = SCPI_StatusQuestionableEnable,},
    {.pattern = "STATus:QUEStionable:ENABle?", .callback = SCPI_StatusQuestionableEnableQ,},

    {.pattern = "STATus:PRESet", .callback = SCPI_StatusPreset,},

    {.pattern = "SOURce[:CALibrator][:MODE]", .callback = SCPI_source_calibrator_mode,},
    {.pattern = "SOURce[:CALibrator][:MODE]?", .callback = SCPI_source_calibrator_modeQ,},
    {.pattern = "SOURce:FREQuency[:FIXed]", .callback = SCPI_source_frequency_fixed,},
    {.pattern = "SOURce:FREQuency[:FIXed]?", .callback = SCPI_source_frequency_fixedQ,},
    {.pattern = "SOURce[:CALibrator]:THD:FREQuency[:FILTred]:MODE", .callback = SCPI_source_calibrator_thd_frequency_filtred_mode,},
    {.pattern = "SOURce[:CALibrator]:THD:FREQuency[:FILTred]:MODE?", .callback = SCPI_source_calibrator_thd_frequency_filtred_modeQ,},
    ...
    // More than 100 scpi_command_t initialization
    ...
    {.pattern = "SYSTem:ERRor:CODE[:NEXT]?", .callback = SCPI_SystemErrorCodeNextQ,},
    {.pattern = "SYSTem:ERRor:ALL?", .callback = SCPI_SystemErrorAllQ,},
    {.pattern = "SYSTem:ERRor:CODE:ALL?", .callback = SCPI_SystemErrorCodeAllQ,},

    SCPI_CMD_LIST_END
};

#endif /* SCPI_COMMANDS_H_ */

With increased commands code will increase 2 times faster. To avoid this you need to define a function in advance, and include the header file here. In general, it's a different story with other pleasures.

But this is all off-topic. If you have any questions or misunderstandings - better write me an email - address in the profile.

karakozov commented 7 years ago

Dmitry, thank you a lot! I suppose that your examples will be useful not only for me.