This page covers SDK features beyond the basics: receiving data from clients, performance profiling, and utilities for timing, serialization, and device configuration.
Consumer Taps
Standard taps send data from your app to a client. Consumer taps do the reverse: they receive data from a client into your app. This enables bidirectional communication, such as receiving control commands or parameter updates while your app is running. The callback runs in its own thread, so if you share state with your main() loop, use appropriate synchronization (e.g., std::mutex, std::atomic). Consumer taps are automatically started on creation and are stopped when the app shuts down.
The example below creates a consumer tap that receives synapse::Tensor messages on a named channel.
bool MyApp::setup() {
// Create a consumer tap that receives Tensor messages
if (!create_consumer_tap<synapse::Tensor>("control_input",
[this](const synapse::Tensor& message) {
// This callback runs in a separate thread
spdlog::info("Received control input with {} dimensions",
message.shape().size());
// Process the incoming message
})) {
spdlog::error("Failed to create consumer tap");
return false;
}
return true;
}Function Profiling
The SDK includes a built-in profiler for measuring the execution time of your functions. This is useful for identifying bottlenecks without adding external tooling.
Setup
Register each profile label with add_profile(), then enable profiling with a reporting interval:
bool MyApp::setup() {
// Register every label you plan to instrument before enabling profiling.
// start_profile() / stop_profile() on an unregistered label log an error and record nothing.
add_profile("frame_acquisition");
add_profile("signal_processing");
add_profile("publish");
// Report profiling stats every 5 seconds
enable_function_profiling(std::chrono::milliseconds(5000));
return true;
}Instrumenting Code
Wrap sections of code with start_profile() and stop_profile() using the labels you registered above:
void MyApp::main() {
while (node_running_) {
start_profile("frame_acquisition");
auto messages = data_reader_->receive_multipart();
stop_profile("frame_acquisition");
if (messages.empty()) {
std::this_thread::sleep_for(std::chrono::microseconds(1));
continue;
}
start_profile("signal_processing");
for (auto& message : messages) {
// Filter and detect spikes
}
stop_profile("signal_processing");
start_profile("publish");
publish_tap("output", output_tensor);
stop_profile("publish");
}
}The profiler tracks call count, min/max/average execution time, and latest sample for each named profile. Results are published to the app_performance_summary tap at the configured interval. To print a specific profile to your app logs on demand:
print_profile("signal_processing");Configuration Utilities
The SDK provides helper functions for reading the device's signal chain configuration. These are useful when your app needs to adapt its behavior based on which nodes are present or how they are configured.
#include <synapse-app-sdk/utils/config/config.hpp>Find nodes by type
// Get all broadband source node IDs
auto node_ids = synapse::get_node_ids_for_type(
device_configuration_, synapse::NodeType::kBroadbandSource);
// Get full node configs for all application nodes
auto app_nodes = synapse::get_nodes_for_type(
device_configuration_, synapse::NodeType::kApplication);Find a specific node
// Look up a node by its ID
auto node = synapse::get_node_by_id(device_configuration_, 1);
if (node.has_value()) {
// Use node.value() to access the config
}Time Utilities
The SDK provides clock functions and a Timer class for rate limiting and interval-based operations:
#include <synapse-app-sdk/utils/time/time.hpp>Timer
Timer tracks elapsed time against a configurable interval:
// Create a timer that fires every 0.1 seconds (10 Hz)
synapse::Timer publish_timer(0.1);
void MyApp::main() {
while (node_running_) {
// Process data...
// Only publish at 10 Hz
if (publish_timer.reset_if_elapsed()) {
publish_tap("output", result);
}
}
}Timer methods:
has_elapsed()— returnstrueif the interval has passedelapsed_seconds()— returns time since last resetreset()— resets the timerreset_if_elapsed()— resets and returnstrueif the interval had passed,falseotherwise
Clock Functions
// Get current steady clock time (monotonic, for measuring durations)
auto now_ns = synapse::get_steady_clock_now();
auto now_us = synapse::get_steady_clock_now<std::chrono::microseconds>();
// Get current system clock time (wall clock, for timestamps)
auto sys_ns = synapse::get_system_clock_now();Data Serialization
Creating outgoing messages
Convert protobuf to ZMQ messages, or pack raw data into Tensor format:
// Protobuf to ZMQ
auto zmq_msg = synapse::protobuf_to_zmq_message(my_protobuf);
// Pack a vector into Tensor binary format
std::vector<float> data = {1.0f, 2.0f, 3.0f};
std::string packed = synapse::pack_tensor_data(data);
// Pack inline values
std::string packed = synapse::pack_tensor_data<float>({x, y});Log Streaming
Application logs (via spdlog) are automatically streamed to the device's log system and can be viewed with synapsectl logs or the Headstage GUI log viewer.
To disable log streaming (e.g., for performance-sensitive applications):
bool MyApp::setup() {
disable_log_streaming();
return true;
}When log streaming is enabled (the default), log messages are published via a tap, making them accessible to any connected client.