j123b567 / scpi-parser

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

Question on Working with Units #76

Closed paulrpotts closed 8 years ago

paulrpotts commented 8 years ago

Hello,

I am testing using scpi-parser to implement commands on ARM SAM4 using the Keil uVision. It builds fine and it is working, but I am trying to figure out the best way to validate user input.

I would like to allow units, but allow certain commands to use only some kinds of units.

For example, if I define:


    {.pattern = "RGB:BRIGHTness:", .callback = SCPI_RGB_Brightness_Set,},
    {.pattern = "RGB:BRIGHTness?", .callback = SCPI_RGB_Brightness_Get,},

And implement:


enum scpi_rgb_channel_number_t {
    SCPI_RGB_UNDEFINED,
    SCPI_RGB_RED,
    SCPI_RGB_GREEN,
    SCPI_RGB_BLUE
};

const scpi_choice_def_t scpi_rgb_channel_numbers_def[] = {
    {/* name */ "RED",      /* type */ SCPI_RGB_RED  },
    {/* name */ "GREEN",    /* type */ SCPI_RGB_GREEN    },
    {/* name */ "BLUE",     /* type */ SCPI_RGB_BLUE    },
    SCPI_CHOICE_LIST_END,
};

/*
    RGB:BRIGHTness [RED|GREEN|BLUE] 0..100%
*/
static scpi_result_t SCPI_RGB_Brightness_Set( scpi_t *context )
{
    scpi_bool_t result;
    int32_t param1_val;
    double param2_val;

    /*
        Two required parameters: channel (RED/GREEN/BLUE) and brightness (percentage)
    */
    result = SCPI_ParamChoice( context, scpi_rgb_channel_numbers_def, &param1_val, TRUE );
    if ( false == result )
    {
        return SCPI_RES_ERR;
    }
    else
    {
        /*
            Percentage is required; don't allow special values. Can we get the parser to
            accept PCT units if supplied? TODO
        */
        scpi_parameter_t param2;

        result = SCPI_Parameter( context, &param2, TRUE );
        if ( false == result )
        {
            return SCPI_RES_ERR;
        }
        else
        {
            /*
                Suffix is allowed
            */
            result = SCPI_ParamIsNumber( &param2, TRUE );
            if ( false == result )
            {
                return SCPI_RES_ERR;
            }
            else
            {
                result = SCPI_ParamToDouble( context, &param2, &param2_val );
                if ( ( false == result ) || ( param2_val < 0.0 ) || ( param2_val > 100.0 ) )
                {
                    /*
                        Not a valid percentage value
                    */
                    return SCPI_RES_ERR;
                }
                else
                {
                    SCPI_ResultBool( context,  1 );
                    return SCPI_RES_OK;

                }

            }

        }

    }

}

This seems to work OK if I test it with:

SCPI_Parse( &scpi_context, "RGB:BRIGHTness RED,25\r\n", strlen("RGB:BRIGHTness RED 25\r\n"));
SCPI_Parse( &scpi_context, "RGB:BRIGHTness RED, 25 PCT\r\n", strlen("RGB:BRIGHTness RED, 25 PCT\r\n"));

It allows me to either specify the units for the second parameter, or not.

What I would like to do is have a way that I could allow units, but detect which units were specified, and return an error if it is not PCT. For example if sometime tries to specify volts for this command, that is not what I intended the value to represent.

Any ideas on how to do that? Is it possible with the library?

I'm not sure how units work and it doesn't seem like the API documentation or examples give me very many clues.

Thanks for any suggestions.

cdwork commented 8 years ago

Hello!

I'm working with SAM4 too. You must enable /* Ratio */ section in units.c. Define USE_UNITS_RATIO for this. After you can use "PCT" suffix.

This code I use in my project: My command: {.pattern = "SOURce[:CALibrator]:THD[:LEVel]", .callback = SCPI_source_calibrator_thd_level,} and method:

scpi_result_t SCPI_source_calibrator_thd_level(scpi_t * context){
    scpi_number_t param;

    // We need mandaratory one parameter
    if (!SCPI_ParamNumber(context,scpi_special_numbers_def,&param,TRUE))
    {
        return SCPI_RES_ERR;
    }

    // Check if received not allowed suffix (different "PCT" or none) 
    if(param.unit!=SCPI_UNIT_NONE && param.unit!=SCPI_UNIT_UNITLESS){
        SCPI_ErrorPush(context,SCPI_ERROR_SUFFIX_NOT_ALLOWED);
        return SCPI_RES_ERR;
    }

    // Percent not must be less than 0 and greater than 1 (usually... ;) )
    if(param.value>1)param.value=1;
    else if(param.value<0)param.value=0;

    // I will bring value to the percentage view.
    float32_t val = (float32_t)(param.value*100.0);

    // Work...
    if((THD_CURRENT_SIGNAL_FEED==thd_signal_u1uh || THD_CURRENT_SIGNAL_FEED==thd_signal_uh) &&
        thd_calibrator_set_thd_level(val,THD_CURRENT_HARMONIC_LEVEL_OFFSET,CURRENT_SPECTRUM.spectrum.k))
    {
        THD_CURRENT_HARMONIC_LEVEL=val;
    }
    return SCPI_RES_OK;
}

And I can use next variants command: "SOURce:CALibrator:THD:LEVel 0.33" or "SOURce:CALibrator:THD:LEVel 33PCT" or "SOURce:CALibrator:THD:LEVel 33 PCT" All interprets sets are equals.

j123b567 commented 8 years ago

Please look inside https://github.com/j123b567/scpi-parser/blob/master/libscpi/src/units.c

What you need is SCPI_ParamNumber

Please ensure that USE_UNITS_RATIO is set to 1 in config.h or in your custom config scpi_user_config.h for PCT unit to work. If you also need precise error reporting, you should enable also USE_FULL_ERROR_LIST because of SCPI_ERROR_DATA_OUT_OF_RANGE.

Getting your second parameter can look like this:

scpi_number_t paramBrightness;
result = SCPI_ParamNumber(context, scpi_special_numbers_def, &paramBrightness, TRUE);
if ( false == result )
{
    return SCPI_RES_ERR;
}

if (paramBrightness.special) {
    switch (paramBrightness.tag) {
    case SCPI_NUM_UP: brightnessInc(color); break;
    case SCPI_NUM_DOWN: brightnessDec(color); break;
    default: 
        SCPI_ErrorPush(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE);
        return SCPI_RES_ERR;
    }
} else {
    // 25 PCT is converted to 0.25 so convert it back
    if (paramBrightness.unit == SCPI_UNIT_UNITLESS) {
        paramBrightness.unit = SCPI_UNIT_NONE;
        paramBrightness.value *= 100;
    }

    if (paramBrightness.unit  == SCPI_UNIT_NONE) {
        if (paramBrightness.value < 0.0 || paramBrightness.value > 100.0) {
             SCPI_ErrorPush(context, SCPI_ERROR_DATA_OUT_OF_RANGE);
            return SCPI_RES_ERR;
        } else {
            brightnessSet(color, paramBrightness.value);
        }
    } else {
        SCPI_ErrorPush(context, SCPI_ERROR_INVALID_SUFFIX);
        return SCPI_RES_ERR;
    }
}
return SCPI_RES_OK;
paulrpotts commented 8 years ago

Thank you for the fast reply! I will try those suggestions and see how it goes.

j123b567 commented 8 years ago

Almost same answers at the same time :-)

Please remember, that SCPI_ParamNumber is converting values for you, please refere units.c for multipliers.

cdwork commented 8 years ago

Yeah, synchronizing :)

paulrpotts commented 8 years ago

Thank you cdwork, it is working perfectly! I can specify 0.25, or 25 PCT, or 25000 PPM, or 25000.0 PPM, and always get back 0.25. That's just what I wanted.

j123b567, thank you too, I will experiment with turning on the extended error range. It looks like this will do just what I want.

paulrpotts commented 8 years ago

I would be happy to contribute some more detailed documentation, if that would be useful.

j123b567 commented 8 years ago

Here is current documentation http://j123b567.github.io/scpi-parser/

You can clone gh-pages branch, extend anything and create pull request. I will be happy to merge it.

I have corrected documentation to correspond with current structure. List of functions is complete but the documentation itself is poor, my English is poor etc.

So any help is welcome.

paulrpotts commented 8 years ago

Great! I will see what I can do. Let me get a little more practice using the library and then maybe I will have more examples.

Your English is a lot better than my German!