Step-by-Step: Console & Simulation Data for Wiper Example#
Note
Step Goal
In this guide, you will learn how to build a console-based simulation for the Wiper example using the SDV framework. The goal is to visualize signal behavior in real time and simulate user interactions like switching between Auto and Manual modes, detecting rain, and activating wipers - all from the terminal. By the end, you’ll be able to run a fully interactive simulation that reflects signal changes and service responses.
Overview#
This page guides you through building a console-based simulation for the Wiper example using the SDV framework. The goal is to visualize signal behavior and simulate interactions such as switching between Auto and Manual modes, activating wipers manually, and responding to rain detection, all within a terminal interface.
Console#
To simulate the functionality and understand how it works, it’s helpful to visualize the signal flow. For that, you can create a Console class that prints the output of the signals.
First, think about what you want to display in the Console output. In my case, for the Wiper example, I want to visualize the Data Dispatch service and the Basic Service. I also want to be able to switch between Auto and Manual modes for the wipers. In Manual mode, I should be able to activate or deactivate the wipers manually. In Auto mode, if it’s raining, the wipers should activate automatically.
So I will have a PrintHeader() function like this:
void CConsole::PrintHeader()
{
// Clear the screen...
std::cout << "\x1b[2J";
// Print the titles
PrintText(g_sTitle, "Wiper Example Functionality");
PrintText(g_sSeparator1, "============================================================================");
PrintText(g_sDispatchService, "Data dispatch service:");
PrintText(g_sSeparator2, "----------------------------------------------------------------------------");
PrintText(g_sBasicService, "Basic services:");
PrintText(g_sSeparator3, "----------------------------------------------------------------------------");
PrintText(g_sUsageMode, "Usage mode:");
PrintText(g_sControlDescription1, "Press 'A' to set Wiper Auto Mode.");
PrintText(g_sControlDescription2, "Press 'M' to set Wiper Manual Mode.");
PrintText(g_sControlDescription3, "Press 'R' to make it a ranny day/ clear day.");
PrintText(g_sControlDescription4, "Press 'W' to activate/deactivate wipers in case of Manual Mode");
PrintText(g_sControlDescription5, "Press 'X' to quit.");
}
Preparing the Data#
Alright, so now that we have the console set up to display things, we need to make sure the data we want to show is actually available. That’s where PrepareDataConsumers() comes in.
Think of it like this: before we can print anything meaningful to the screen, we need to connect to the services and devices that provide the data. This function does exactly that, it grabs the interfaces for the rain sensor, wiper mode, and front/rear wipers, both from the Basic Services and the Vehicle Devices.
Let’s walk through it step by step.
Step 1: Get the Wiper Mode from the Basic Service#
auto pBSWiperMode = sdv::core::GetObject("Vehicle.Body.Windshield.Wiper.Mode_Service")
.GetInterface<vss::Vehicle::Body::Windshield::Wiper::ModeService::IVSS_GetMode>();
This line tries to get the interface for reading the current wiper mode. If it fails, we log an error. If it works, we print the mode in the console.
Step 2: Get the Rain Detection from the Basic Service#
auto pBSWeatherRain = sdv::core::GetObject("Vehicle.Body.Weather.Rain_Service")
.GetInterface<vss::Vehicle::Body::Weather::RainService::IVSS_GetDetected>();
Same idea here, we want to know if it’s raining. If the service is available, we read the value and print it.
Step 3: Set the Front and Rear Wipers to Inactive#
SetIsFrontActive(false);
SetIsRearActive(false);
We do this to make sure the system starts in a clean state.
Step 4: Register Events from the Vehicle Devices#
auto pVDWiperMode = sdv::core::GetObject("Vehicle.Body.Windshield.Wiper.Mode_Device")
.GetInterface<vss::Vehicle::Body::Windshield::Wiper::ModeDevice::IVSS_Mode>();
pVDWiperMode->RegisterModeEvent(dynamic_cast<vss::Vehicle::Body::Windshield::Wiper::ModeDevice::IVSS_WriteMode_Event*>(this));
This part is important. It tells the system: “Hey, if the mode changes, notify me!”
We do the same for rain detection.
auto pVDWeatherRain = sdv::core::GetObject("Vehicle.Body.Weather.Rain_Device")
.GetInterface<vss::Vehicle::Body::Weather::RainDevice::IVSS_Detected>();
pVDWeatherRain->RegisterDetectedEvent(dynamic_cast<vss::Vehicle::Body::Weather::RainDevice::IVSS_WriteDetected_Event*>(this));
Step 5: Register Signals#
Finally, we call
RegisterSignals();
Register Signals#
Once the services and devices are loaded, we need to connect our application to the signal bus. That’s exactly what RegisterSignals() does.
Let’s break it down.
Step 5.1: Create a Dispatch Service
sdv::core::CDispatchService dispatch;
This object lets you subscribe to signals and register them for transmission or reception. It’s your gateway to the SDV signal bus.
Step 5.2: Subscribe to RX Signals
These are signals your app wants to listen to.
m_signalRainDetected = dispatch.Subscribe(wiper::dsRainDetected, [&{
CallbackRainDetected(value);
});
m_signalWiperMode = dispatch.Subscribe(wiper::dsWiperMode, & {
CallbackWiperMode(value);
});
Here, we’re saying: “Whenever the rain or wiper mode changes, call my callback function.”
These callbacks update internal state and later help us decide what to print in the console.
Step 5.3: Register TX Signals
These are signals your app wants to send out.
m_signalFrontWiperActive = dispatch.RegisterTxSignal(wiper::dsFrontWiperActive, 0);
m_signalRearWiperActive = dispatch.RegisterTxSignal(wiper::dsRearWiperActive, 0);
The 0 here is the default value. You can update these signals later when the user presses a key or when rain is detected.
Step 5.4: Log for Debugging
SDV_LOG_INFO("RegisterSignals called");
Always good to log this for troubleshooting. If something goes wrong, you’ll know whether signal registration was reached.
Step 5.5: Callback functions
When subscribing to signals using the DispatchService, we provide callback functions that get triggered when the signal value changes. These callbacks are essential for updating the internal state and refreshing the console display.
CallbackRainDetected
This function is called whenever the dsRainDetected signal changes.
void CConsole::CallbackRainDetected(sdv::any_t value)
{
m_IsRainDetected = value.get<bool>();
UpdateData();
}
It extracts the boolean value from the signal and updates the m_IsRainDetected flag. Then it calls UpdateData() to refresh the console.
CallbackWiperMode
This function is triggered when the dsWiperMode signal changes.
void CConsole::CallbackWiperMode(sdv::any_t value)
{
m_WiperMode = value.get<std::string>();
UpdateData();
}
It updates the internal m_WiperMode string and refreshes the console display.
These callbacks ensure that the console always reflects the latest signal values in real time.
Step 6: Print Initial Values#
After registration, we can show the current state in the console:
//Data link
PrintText(g_dsWiperModeEvent, "Wiper mode: ............. " + m_WiperMode + " ");
PrintValue(g_dsWeatherRain, "Weather: ", m_IsRainDetected, (m_IsRainDetected ? "Raining" : "Clear/dry"));
PrintValue(g_dsWiperFrontActive, "Front Wiper: ", m_WiperFrontIsActive, (m_WiperFrontIsActive ? "Active" : "Inactive"));
PrintValue(g_dsWiperRearActive, "Rear Wiper: ", m_WiperRearIsActive, (m_WiperRearIsActive ? "Active" : "Inactive"));
//Basic service signals
PrintText(g_bsWiperModeEvent, "Wiper mode: ............. " + m_WiperMode + " ");
PrintValue(g_bsWeatherRain, "Weather: ", m_IsRainDetected, (m_IsRainDetected ? "Raining" : "Clear/dry"));
PrintValue(g_bsWiperFrontActive, "Front Wiper: ", m_WiperFrontIsActive, (m_WiperFrontIsActive ? "Active" : "Inactive"));
PrintValue(g_bsWiperRearActive, "Rear Wiper: ", m_WiperRearIsActive, (m_WiperRearIsActive ? "Active" : "Inactive"));
This gives the user a snapshot of the system state right after startup.
Step 7: Runtime loop for simulation#
The RunUntilBreak() function is the heart of the runtime loop. It listens for keyboard input and updates the signal states accordingly. This allows you to simulate your implementation by sending inputs directly from the keyboard to the dispatch signals.
char c = GetChar();
switch (c)
{
case 'a': case 'A':
m_signalWiperMode.Write<std::string>("auto");
autoMode = true;
break;
case 'm': case 'M':
m_signalWiperMode.Write<std::string>("manual");
autoMode = false;
break;
case 'r': case 'R':
rainDetected = !rainDetected;
m_signalRainDetected.Write<bool>(rainDetected);
if (autoMode) {
wiperActive = rainDetected;
}
break;
case 'w': case 'W':
wiperActive = !wiperActive;
cManual.SetManualFrontActive(wiperActive);
cManual.SetManualRearActive(wiperActive);
break;
case 'x': case 'X':
bRunning = False;
break;
default:
break;
}
Step 8: Update data in Console#
After each signal change, you should update the console display to reflect the current state of the system.
You can choose what to display.
Dispatch (Data Link) signal values - read directly from the signal objects
PrintText(g_dsWiperModeEvent, "Wiper mode: ............. " + m_signalWiperMode.Read().get<std::string>());
Basic Service values - stored in internal variables updated via callbacks.
PrintText(g_bsWiperModeEvent, "Wiper mode: ............. " + m_WiperMode);
Step 9: Reset Signals#
This function is called at the end of the application to clean up all signal subscriptions and transmissions.
void CConsole::ResetSignals()
{
m_signalRainDetected.Reset();
m_signalWiperMode.Reset();
m_signalFrontWiperActive.Reset();
m_signalRearWiperActive.Reset();
}
This ensures that no lingering signal connections remain after the application exits, which is important for clean shutdown and avoiding memory leaks.
Step 10: Main function#
Now that we have built all the pieces: signal registration, data consumers, console display, and the runtime loop. We need a main() function to bring everything together.
Think of main() as the conductor of your SDV simulation. It calls each part in the right order so the system starts correctly, runs smoothly, and shuts down cleanly.
Here is how it works:
int main()
{
std::cout << "Main call" << std::endl;
CWiperControl appobj;
if (!appobj.Initialize())
{
std::cout << "ERROR: Failed to initialize application control." << std::endl;
return 1;
}
else
{
std::cout << "Initialize application control with success." << std::endl;
}
CConsole visual_obj;
visual_obj.PrintHeader();
visual_obj.PrepareDataConsumers();
visual_obj.InitData();
appobj.SetRunningMode();
appobj.RunUntilBreak();
visual_obj.ResetSignals();
appobj.Shutdown();
return 0;
}
Why this order?
We initialize the app first to load configs and register signals.
Then we set up the console so the user can interact.
We switch to running mode and start listening for input.
Finally, we clean up everything before exiting.
This structure ensures your simulation behaves predictably and cleanly from start to finish.
Note
Step Reach
After completing this guide, you now know how to:
Create a console interface to display signal states and user instructions.
Connect to SDV services and devices to retrieve and update signal data.
Register RX and TX signals and handle them with callback functions.
Simulate user input to control wiper behavior in Auto and Manual modes.
Cleanly reset signals and shut down the application.
You’ve successfully built a simulation that helps visualize and test your SDV application logic in a user-friendly way. Awesome job!