Skip to content

Cyclic Data Exchange


Once up and running JCS performs synchronous cyclic data exchange with all devices and processes on the network.

Cyclic data exchange involves the user updating dev_host with any new signals to propagate to the network, ticking dev_host once, then retrieving any signals from the network.

Ticking the system over in a real-time thread might look like:

while (do_running) {
    task_rt::wait_next_cycle_rt(&thread_host, cycle_time_ns);

    // If dev_host is running and exchanging data
    // write out commands to the network
    if (host->data_is_valid_rt()) {
        host->sig_input_set_rt(0, 0, input_command);
    }

    // Always tick dev_host
    if (host->step_rt(&cycle_time_ns) != jcs::RET_OK) {
        // Handle error and break from loop
        do_running = false;
    }

    // If dev_host is running and exchanging data
    // fetch any data from the network
    if (host->data_is_valid_rt()) {
        host->sig_output_get_rt(0, 0, &output_data);
    }
}

From this example we can see that ticking the JCS system and getting data into and out of the network is straight forward; all dev_host work is done in the function step_rt() and synchronous signal access is performed by the sig_output_get_rt and sig_output_get_rt functions.


Signals and signal API

A JCS signal is a quantity of interest that may be routed locally on the devices, or around the network to be consumed by other devices, processes and jcs_host. Signals form the primary method of the user getting data into and out of a JCS system.

Signals are transferred into and out of JCS by functions that access the signals by the data type.

Currently, JCS supports signal types:

Signal type Description
float32 Floating point value.
Converted from native float size to 32 bit float when transmitted from dev_host to devices.
Converted from 32 bit float to native float size when received from devices to dev_host.
uint32 Unsigned 32 bit integer.
uint16 Unsigned 16 bit integer.
uint8 Unsigned 8 bit integer.

Signal access indexing

To achieve real-time performance, choosing the correct underlying signal or rate is achieved by indexing.

For example, sig_output_get_rt() for a single value requires a rate index and a signal index.

Rates indexing

Rates are indexed in the order they appear in dev_host.yaml.

Use sig_input_rate_sz_rt() and sig_output_rate_sz_rt() to get the total number of rates for incoming and outgoing data.

Signals indexing

Signals indexing is a bit more tricky, as they need not appear in structure.yaml in any particular order.

Use sig_input_sz_rt() and sig_output_sz_rt() to get the total number of signals for a rate of a given type.

For example:

if (host_->sig_output_sz_rt(jcs::signal_type::float32_s, 0, &base_rate_float32_size) != jcs::RET_OK) {
    // Handle error
}

It may be useful to get the signal index by signal name. Use sig_input_index_get and sig_output_index_get.

For example:

if (host_->sig_input_index_get(jcs::signal_type::float32_s, 0, "host_i_d", &sig_index_i_d_) != jcs::RET_OK) {
    // Handle error
}

Signal access

Signal access is performed via dev_host functions sig_input_set_rt() and sig_output_get_rt().

sig_input_set_rt

This function writes data into dev_host for transmission to devices and processes at the next call to step_rt(). Access is via data type.

Two versions of the function exist: Access via std::vector and access via single value.

sig_input_set_rt access via std::vector

These functions allow writing a std::vector of data of a particular type to dev_host and is probably the most useful way to get data into dev_host. Writing signals via std::vector requires the following:

rate_idx An unsigned int that indexes into the desired rate.
Use sig_input_rate_sz_rt() to get the total size of the rates.
Base rate is always index 0.
std::vector A vector of values of the type to transfer to JCS.
The vector passed to sig_input_set_rt() must be the same size as number of signals for that rate and type. Use sig_input_sz_rt() to get the required vector size.

For example:

// Create a vector for storage the same size as the number of float signals for base rate
std::vector signals_in_f;
signals_in_f.resize( host_->sig_input_sz_unsafe_rt(jcs::signal_type::float32_s, 0) );
...
// Step the current ramp and store new value for writing to dev_host
signals_in_f[sig_index_i_d_] = i_ramp_->step();
...
// Write base rate signals out to dev_host
host_->sig_input_set_rt(0, signals_in_f);
sig_input_set_rt access via single value

These functions allow writing a single value of data of a particular type to dev_host. Writing signals via single value requires the following:

rate_idx An unsigned int that indexes into the desired rate.
Use sig_input_rate_sz_rt() to get the total size of the rates.
Base rate is always index 0.
idx An unsigned int that indexes into the desired signal.
in A value of the type to transfer to JCS.

For example:

// Get the speed setpoint signal index
unsigned int sig_index_w;
if (host_->sig_input_index_get(jcs::signal_type::float32_s, 0, "host_w", &sig_index_w) != jcs::RET_OK) {
    // Handle error
}
...
// Write base rate speed setpoint out to dev_host
host_->sig_input_set_rt(0, sig_index_w, 10.0f);

sig_output_get_rt

This function reads data from dev_host that comes from devices and processes after the previous call to step_rt(). Access is via data type.

Two versions of the function exist: Access via std::vector and access via single value.

sig_output_get_rt access via std::vector

These functions allow reading a std::vector of data of a particular type from dev_host and is probably the most useful way to get data out of dev_host. Reading signals via std::vector requires the following:

rate_idx An unsigned int that indexes into the desired rate.
Use sig_output_rate_sz_rt() to get the total size of the rates.
Base rate is always index 0.
std::vector A vector of values of the type to read from JCS.
The vector passed to sig_output_get_rt() must be the same size as number of signals for that rate and type. Use sig_output_sz_rt() to get the required vector size.

For example:

// Create a vector for storage the same size as the number of float signals for base rate
std::vector signals_out_f;
signals_out_f.resize( host_->sig_output_sz_unsafe_rt(jcs::signal_type::float32_s, 0) );
...
// Read base rate signals from dev_host
host_->sig_input_set_rt(0, signals_out_f);
sig_output_get_rt access via single value

These functions allow reading a single value of data of a particular type from dev_host. Reading signals via single value requires the following:

rate_idx An unsigned int that indexes into the desired rate.
Use sig_output_rate_sz_rt() to get the total size of the rates.
Base rate is always index 0.
idx An unsigned int that indexes into the desired signal.
out A value of the type to read from JCS.

For example:

// Get the speed setpoint signal index
float joint_0_th;
unsigned int sig_index_j0_th;
if (host_->sig_output_index_get(jcs::signal_type::float32_s, 0, "host_joint_0_th", &sig_index_j0_th) != jcs::RET_OK) {
    // Handle error
}
...
// Read base rate joint 0 theta from dev_host
host_->sig_output_get_rt(0, sig_index_j0_th, &joint_0_th);

Signal validity

During it's lifetime, a signal is produced by a device or process then traverses the network, where (if mapped) it will arrive at dev_host. Once the signal arrives at dev_host it becomes valid. Once the user makes a call to sig_output_get_rt() the signal is flagged as stale. If a call is made to sig_output_get_rt() for a signal that is invalid or stale, the previous valid data will be returned.

Check for signal validity with calls to sig_output_is_valid_unsafe_rt(). Check for signal stale-ness with calls to sig_output_is_stale_unsafe_rt().

It is not necessary to check base rate signals for validity or stale-ness.

Signal API helper functions

dev_host features a number of useful tools for getting signal names, units and limits.

Opstate signals

Device opstate signals are always returned to dev_host at the base rate. Access the device opstate signals sig_opstate_output_get() as a std::vector or single value.

The opstate vector and single index indices are ordered as the devices appear in structure.yaml.

Opstate signal and device names are available via the sig_opstate_output_name_get() and sig_opstate_output_node_name_get() helper functions.