Handling a blocking service callback
I have a ROS2 service node that should start/stop camera recording using Gstreamer when a remote control button is pressed.
The first issue I am facing is that the "start recording function" call in the service callback is a blocking call. It will keep polling to check if the recording is stopped or not. In other words, when the service callback is called with a "start" command, it will start recording (probably forever). So, when recording starts and the client calls the server with a "stop" command, I would like another service callback in another thread that forces recording to stop (using a shared pipeline variable).
my service callback looks like this now:
// global variables
GstElement *pipeline;
GstBus *bus;
GstMessage *msg;
static bool is_pipeline_playing = false;
void capture(const std::shared_ptr<rc_cam_interface::srv::RcCam::Request> request,
std::shared_ptr<rc_cam_interface::srv::RcCam::Response> response)
{
// TODO: this should depend on the output of the pipeline
response->success = true;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request with name: " + request->command);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response");
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Thread id is %u", std::hash<std::thread::id>{}(std::this_thread::get_id()));
if ("start" == request->command && is_pipeline_playing)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "already recording!");
return;
}
if ("stop" == request->command && !is_pipeline_playing)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "recording already stopped!");
return;
}
if ("stop" == request->command && is_pipeline_playing)
{
gst_element_set_state(pipeline, GST_STATE_NULL);
}
if ("start" == request->command && !is_pipeline_playing)
{
/* Initialize GStreamer */
gst_init(nullptr, nullptr);
std::string file_name = request->timestamp;
std::string pipeline_description = "gst-launch-1.0 v4l2src ! video/x-raw,width=640,height=480,framerate=30/1 ! vpuenc_h264 bitrate=500 ! rtph264pay ! filesink location=" + file_name;
//pipeline string
/* Build the pipeline */
pipeline =
gst_parse_launch(pipeline_description.c_str(), // The type of pipeline description is gchar* which is a typedef of char*, this converts std::string to char*
NULL);
/* Start playing */
gst_element_set_state(pipeline, GST_STATE_PAUSED);
if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Could not start stream!");
}
is_pipeline_playing = true;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "pipeline running");
/* Wait until error or EOS */
bus = gst_element_get_bus(pipeline);
msg =
gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
((GstMessageType)((GstMessageType)GST_MESSAGE_ERROR | (GstMessageType)GST_MESSAGE_EOS)));
is_pipeline_playing = false;
/* Free resources */
if (msg != NULL)
{
gst_message_unref(msg);
}
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "gst resources are freed");
}
}
My client node has a subscriber to the remote control and when the button is pressed, it calls the camera service in the subscriber callback function. Now, if the client is blocked because it is waiting for the service to finish, how would I call the subscriber callback again if it blocked because of the blocked client.
I would be happy to hear another way of designing this.
The question title is a bit of an xy-problem, but the functionality you describe seems perfect to wrap in an action.
Cancelling the goal would then result in stopping the recording.