Page tree
Skip to end of metadata
Go to start of metadata

Introduction

In this article we will make an example of UAVCAN service.

Goal

The most obvious example of service in UAVCAN is node configuration. We will use uavcan.protocol.param.GetSet data type to read and write some parameters in Babel.

Implementation

So let's give our Zubax Babel some parameters that we will try to configure from UAVCAN GUI Tool. For the sake of simplicity in this example we will have only integer numeric parameters though UAVCAN allows you to have parameters of just any type you want.

parameters type
typedef struct
{
    uint8_t * name;
    int64_t val; 
    int64_t min;
    int64_t max;
    int64_t defval;
} param_t;

In this example we will have three integer parameters. It is not necessary but mey be considered good practice to specify default value and acceptable value range for numeric parameters.

parameters
static param_t parameters[] = 
{
    {"param0", 0, 10,20, 15},
    {"param1", 1, 0, 100, 25},
    {"param2", 2, 2, 8,  3 },
};

We will also need a couple ways to access tgese parameters: by index and by name with some safety checks.

parameters access
static inline param_t * GetParamByIndex(uint16_t index)
{
    if(index >= ARRAY_SIZE(parameters)) 
    {
        return NULL;
    }
    return &parameters[index];
}


static inline param_t * GetParamByName(uint8_t * name)
{
    for(uint16_t i = 0; i < ARRAY_SIZE(parameters); i++)
    {
        if(strncmp(name, parameters[i].name,strlen(parameters[i].name)) == 0) 
        {
              return &parameters[i];
        }
    }  
    return NULL;
}

Now we need to add new type of UAVCAN messages that we want to process to shouldAcceptTransfer and add its handler call to onTransferReceived.

shouldAcceptTransfer
bool shouldAcceptTransfer(const CanardInstance* ins,
                          uint64_t* out_data_type_signature,
                          uint16_t data_type_id,
                          CanardTransferType transfer_type,
                          uint8_t source_node_id)
{
    if ((transfer_type == CanardTransferTypeRequest) &&
        (data_type_id == UAVCAN_GET_NODE_INFO_DATA_TYPE_ID))
    {
        *out_data_type_signature = UAVCAN_GET_NODE_INFO_DATA_TYPE_SIGNATURE;
        return true;
    }

    if(data_type_id == UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_ID)
    {
        *out_data_type_signature = UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_SIGNATURE;
        return true;
    }
    
    if(data_type_id == UAVCAN_PROTOCOL_PARAM_GETSET_ID)
    {
        *out_data_type_signature = UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE;
        return true;
    }
    return false;
}


onTransferReceived
void onTransferReceived(CanardInstance* ins, CanardRxTransfer* transfer)
{
    if ((transfer->transfer_type == CanardTransferTypeRequest) &&
    (transfer->data_type_id == UAVCAN_GET_NODE_INFO_DATA_TYPE_ID))
    {
        canard_get_node_info_handle(transfer);
    } 
    
    if(transfer->data_type_id == UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_ID)
    { 
        canard_rawcmd_handle(transfer);
    }
    
    if(transfer->data_type_id == UAVCAN_PROTOCOL_PARAM_GETSET_ID)
    { 
        canard_getset_handle(transfer);
    }
}


We should also write handler for UAVCAN_PROTOCOL_PARAM_GETSET_ID. Here it is:

canard_getset_handle
void canard_getset_handle(CanardRxTransfer* transfer)
{
    uint16_t index = 0xFFFF;
    uint8_t tag    = 0;
    int offset     = 0;
    int64_t val    = 0;

    canardDecodeScalar(transfer, offset,  13, false, &index);
    offset += 13;
    canardDecodeScalar(transfer, offset, 3, false, &tag);
    offset += 3;

    if(tag == 1)
    {
        canardDecodeScalar(transfer, offset, 64, false, &val);
        offset += 64;
    } 

    uint16_t n = transfer->payload_len - offset / 8 ;
    uint8_t name[16]      = "";
    for(int i = 0; i < n; i++)
    {
        canardDecodeScalar(transfer, offset, 8, false, &name[i]);
        offset += 8;
    }

    param_t * p = NULL;

    if(strlen((char const*)name)) 
    {
        p = GetParamByName(name); //we should always try access by name first
    }
    else
    {
        p = GetParamByIndex(index);  //If no name was provided - try access by index
    }

    if((p)&&(tag == 1))
    {
        p->val = val;
    }

    uint8_t  buffer[64] = "";
    uint16_t len = canardEncodeParam(p, buffer);
    int result = canardRequestOrRespond(&canard,
                                        transfer->source_node_id,
                                        UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE,
                                        UAVCAN_PROTOCOL_PARAM_GETSET_ID,
                                        &transfer->transfer_id,
                                        transfer->priority,
                                        CanardResponse,
                                        &buffer[0],
                                        (uint16_t)len);
  
}

We also need to create a function that will encode integer param to uavcan.protocol.param.GetSet message. It will be a little nasty as bit operations always are, but here it is.

canardEncodeParam
uint16_t canardEncodeParam(param_t * p, uint8_t * buffer)
{
uint8_t n     = 0;
int offset    = 0;
uint8_t tag   = 1;
if(p==NULL)
{   
    tag = 0;
    canardEncodeScalar(buffer, offset, 5, &n);
    offset += 5;
    canardEncodeScalar(buffer, offset,3, &tag);
    offset += 3;
    
    canardEncodeScalar(buffer, offset, 6, &n);
    offset += 6;
    canardEncodeScalar(buffer, offset,2, &tag);
    offset += 2;
    
    canardEncodeScalar(buffer, offset, 6, &n);
    offset += 6;
    canardEncodeScalar(buffer, offset, 2, &tag);
    offset += 2;
    buffer[offset / 8] = 0;
    return ( offset / 8 + 1 );
}
canardEncodeScalar(buffer, offset, 5,&n);
offset += 5;
canardEncodeScalar(buffer, offset, 3, &tag);
offset += 3;
canardEncodeScalar(buffer, offset, 64, &p->val);
offset += 64;

canardEncodeScalar(buffer, offset, 5, &n);
offset += 5;
canardEncodeScalar(buffer, offset, 3, &tag);
offset += 3;
canardEncodeScalar(buffer, offset, 64, &p->defval);
offset += 64;

canardEncodeScalar(buffer, offset, 6, &n);
offset += 6;
canardEncodeScalar(buffer, offset, 2, &tag);
offset += 2;
canardEncodeScalar(buffer, offset, 64, &p->max);
offset += 64;

canardEncodeScalar(buffer, offset, 6, &n);
offset += 6;
canardEncodeScalar(buffer, offset,2,&tag);
offset += 2;
canardEncodeScalar(buffer, offset,64,&p->min);
offset += 64;

memcpy(&buffer[offset / 8], p->name, strlen((char const*)p->name));
/* See important note below */
return  (offset/8 + strlen((char const*)p->name)); 
}

Important note According to UAVCAN DSDL specification, section Dynamic arrays, there should be a bit field(often 8 bits wide) representing the length of the array prepending array field. But there is one important detail, which plays role in this particular case. DSDL also describes tail array optimization which means that in case when array is the last field in the UAVCAN message there is no need to specify its length and it must be skipped. That is why in the function above we did not specify length of the parameter name

Now its time to go to UAVCAN GUI Tool and check our newly-created parameters. Double-click on the node record in the online nodes table and will see Node properties window. Click button Fetch all to read all parameters from Babel. You should see something like this:

Now double click on any paramter and try changing its value.

Now close Node properties window and re-open it and refetch all the parameters to make sure you actually updated parameter value.

  • No labels