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
andinterface_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_id
and 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 callncodec_read()
to read messages. Repeat calls until the function returns-ENOMSG
.ncodec_truncate()
- Models callncodec_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 callncodec_write()
to write messages. Can be called several times.ncodec_flush()
- Models callncodec_flush()
to copy the buffered writes to the Binary Signal. Typically called at the end of a simulation step, howeverncodec_flush()
can be called many times; each time its called the accrued content from prior calls toncodec_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
)