Model C with Network Codec

Network Codec

The Model C Library integrates the DSE Network Codec implementation of the Automotive Bus schemas.

Configuration of Binary Signals

IMPORTANT: It is recommended to specify a SignalGroup for each individual Model Instance in a Simulation. This is so that the MIMEtype of each Binary Signal can be completely configured (especially bus_id,node_id and interface_id). Models may implement supplemental configuration options (such as annotations on the Model Instance definition) which can further adjust or augment the MIMEtype parameters.

Binary signals are configured with a MIME Type. The Signal Vector integration with the Network Codec will automatically open codec objects for each supported MIME Type that is supported (by the codec). Additional configuration of the codec objects can be done with the ncodec_config() API function.

kind: SignalGroup
metadata:
  name: network
  labels:
    channel: network_vector
  annotations:
    vector_type: binary
spec:
  signals:
    - signal: can_bus
      annotations:
        mime_type: application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=3

Additional configuration information is available here. Especially the behaviour of bus_id,node_idand interface_id configuration items are described.

Configuration items can also be set at runtime with the ncodec_config() API as the following example shows:

#include <string.h>
#include <dse/logger.h>
#include <dse/modelc/model.h>
#include <dse/ncodec/codec.h>

void configure_codec(ModelDesc* m, SignalVector* sv, uint32_t idx)
{
    NCODEC*     nc = sv->vtable.codec(sv, idx);
    const char* node_id = NULL;
    for (int i = 0; i >= 0; i++) {
        NCodecConfigItem nci = ncodec_stat(nc, &i);
        if (strcmp(nci.name, "node_id") == 0) {
            node_id = nci.value;
            break;
        }
    }
    if (node_id == NULL) {
        node_id = model_instance_annotation(m, "node_id");
        if (node_id == NULL) log_fatal("No node_id configuration found!");
        ncodec_config(nc, (struct NCodecConfigItem){
                              .name = "node_id",
                              .value = node_id,
                          });
    }
}

Tracing NCodec Frames

The ModelC runtime can enable tracing for selected (or all) frames being sent or received by a Model Instance. This tracing is enabled via environment variables.

Example Trace

For the Binary Signal described by the following MIMEtype:

application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=3

The ModelC command would be (setting the environment variable directly):

NCODEC_TRACE_CAN_1=0x3ea,0x3eb modelc --name=ncodec_inst ...

or

NCODEC_TRACE_CAN_1=* modelc --name=ncodec_inst ...

Usage in Model Code

The Network Codec integration is fairly easy to use. The general approach is as follows:

#include <dse/modelc/model.h>
#include <dse/ncodec/codec.h>

extern int put_rx_frame_to_queue(uint32_t, uint8_t*, size_t);
extern int get_tx_frame_from_queue(uint32_t*, uint8_t**, size_t*);

typedef struct {
    ModelDesc model;
    NCODEC*   nc;
} ExtendedModelDesc;

int model_step(ModelDesc* model, double* model_time, double stop_time)
{
    ExtendedModelDesc* m = (ExtendedModelDesc*)model;
    NCodecCanMessage   msg = {};

    /* Message RX. */
    while (1) {
        if (ncodec_read(m->nc, &msg) < 0) break;
        put_rx_frame_to_queue(msg.frame_id, msg.buffer, msg.len);
    }

    /* Message TX. */
    ncodec_truncate(m->nc); /* Clear the codec buffer (i.e. Rx data). */
    while (get_tx_frame_from_queue(&msg.frame_id, &msg.buffer, &msg.len)) {
        ncodec_write(m->nc, &msg);
    }
    ncodec_flush(m->nc);

    /* Progress the Model time. */
    *model_time = stop_time;
    return 0;
}

For most use cases, the call sequence during a simulation step will be:

  • ncodec_seek() - Models typically do not call this method! This call sets up the Binary Signal for reading and will be called automatically by the ModelC runtime.
  • ncodec_read() - Models call ncodec_read() to read messages. Repeat calls until the function returns -ENOMSG.
  • ncodec_truncate() - Models call ncodec_truncate() to prepare the Binary Signal for writing. Mandatory, call even if not intending to write frames to the NCodec in the current simulation step.
  • ncodec_write() - Models call ncodec_write() to write messages. Can be called several times.
  • ncodec_flush() - Models call ncodec_flush() to copy the buffered writes to the Binary Signal. Typically called at the end of a simulation step, however ncodec_flush() can be called many times; each time its called the accrued content from prior calls to ncodec_write() are appended to the Binary Signal.

Usage in Test Cases

When developing Test Cases its possible to use an existing Network Codec object to send or receive messages with the objects owner (usually a model). This is particularly useful for testing messaging behaviour of a model under specific circumstances. When doing this its necessary to circumvent the node_id filtering of that codec, as the following example illustrates.

#include <dse/testing.h>
#include <dse/modelc/runtime.h>
#include <dse/ncodec/codec.h>

typedef struct ModelCMock {
    SimulationSpec     sim;
    ModelInstanceSpec* mi;
} ModelCMock;

char* get_ncodec_node_id(NCODEC* nc)
{
    assert_non_null(nc);
    int         index = 0;
    const char* node_id = NULL;
    while (index >= 0) {
        NCodecConfigItem ci = ncodec_stat(nc, &index);
        if (strcmp(ci.name, "node_id") == 0) {
            node_id = ci.value;
            break;
        }
        index++;
    }
    if (node_id) return strdup(node_id);
    return NULL;
}

void set_ncodec_node_id(NCODEC* nc, const char* node_id)
{
    assert_non_null(nc);
    ncodec_config(nc, (struct NCodecConfigItem){
                          .name = "node_id",
                          .value = node_id,
                      });
}

void test_message_sequence(void** state)
{
    ModelCMock*   mock = *state;
    SignalVector* sv = mock->mi->model_desc->sv;
    NCODEC*       nc = sv->vtable.codec(sv, 2);
    const char*   buffer = "hello world";

    // ...

    // Modify the node_id.
    char* node_id_save = get_ncodec_node_id(nc);
    set_ncodec_node_id(nc, "42");
    // Send a message (which will not be filtered).
    ncodec_write(nc, &(struct NCodecCanMessage){
                         .frame_id = 42,
                         .buffer = (uint8_t*)buffer,
                         .len = strlen(buffer),
                     });
    ncodec_flush(nc);
    // Restore the existing node_id.
    set_ncodec_node_id(nc, node_id_save);
    free(node_id_save);

    // ...
}

Build Integration

The Network Codec integration repackages the necessary include files with the Model C Library packages. No additional build integration is required. A typical CMake configuration might look like this:

FetchContent_Declare(dse_modelc_lib
    URL                 ${MODELC_LIB__URL}
    SOURCE_DIR          "$ENV{EXTERNAL_BUILD_DIR}/dse.modelc.lib"
)
FetchContent_MakeAvailable(dse_modelc_lib)
set(DSE_MODELC_INCLUDE_DIR "${dse_modelc_lib_SOURCE_DIR}/include")

# ... dynamic linked Model (ModelC.exe provides objects) ...

target_include_directories(some_target
    PRIVATE
        ${DSE_MODELC_INCLUDE_DIR}
        ../..
)

# ... static linked target (typical CMocka test application) ...

set(MODELC_BINARY_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse.modelc.lib")
find_library(MODELC_LIB
    NAMES
        libmodelc_bundled.a
    PATHS
        ${MODELC_BINARY_DIR}/lib
    REQUIRED
    NO_DEFAULT_PATH
)
add_library(modelc STATIC IMPORTED GLOBAL)
set_target_properties(modelc
    PROPERTIES
        IMPORTED_LOCATION "${MODELC_LIB}"
        INTERFACE_INCLUDE_DIRECTORIES "${MODELC_BINARY_DIR}"
)
set(DSE_MODELC_LIB_INCLUDE_DIR "${MODELC_BINARY_DIR}/include")
add_executable(test_mstep
    mstep/__test__.c
    mstep/test_mstep.c
)
target_include_directories(test_mstep
    PRIVATE
        ${DSE_MODELC_LIB_INCLUDE_DIR}
        ./
)
target_link_libraries(test_mstep
    PUBLIC
        -Wl,-Bstatic modelc -Wl,-Bdynamic ${CMAKE_DL_LIBS}
    PRIVATE
        cmocka
        dl
        m
)