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.