IRSOL
C++ code implementing socket server for interacting with Baumer camera.
viewer_client_gis.cpp File Reference

Example client that sends gis (get images) commands to the camera server. More...

#include "irsol/args.hpp"
#include "irsol/irsol.hpp"
#include <atomic>
#include <csignal>
#include <opencv2/opencv.hpp>
#include <optional>
#include <regex>

Go to the source code of this file.

Functions

void signalHandler (int signal)
 
bool configureGis (irsol::types::connector_t &conn, double fps, uint64_t isl)
 
std::optional< std::vector< std::pair< size_t, cv::Mat > > > queryImages (irsol::types::connector_t &conn)
 
std::optional< irsol::types::connector_tcreateConnectionWithRetry (const std::string &host, irsol::types::port_t port, std::chrono::seconds retryTimeout=std::chrono::seconds(1))
 
void run (double inFps, uint64_t sequenceLength)
 
int main (int argc, char **argv)
 

Variables

std::atomic< bool > g_terminate {false}
 

Detailed Description

Example client that sends gis (get images) commands to the camera server.

This executable connects to the camera server and requests a stream of images using the gis command, specifying the desired frame rate and sequence length. Each received image is displayed using OpenCV, with annotations for image ID and index.

Command-line options: -f, –fps <fps> Set the requested stream FPS (default: 0.5 FPS) -i, –isl <isl> Set the input sequence length (default: 10)

Usage: ./06-client-server-interaction-image-commands-viewer-gis [-f <fps>] [-i <isl>]

The client can be interrupted with Ctrl+C or by pressing 'q' in the OpenCV window. All logging is written to logs/viewer-client-gis.log.

Definition in file viewer_client_gis.cpp.

Function Documentation

◆ configureGis()

bool configureGis ( irsol::types::connector_t conn,
double  fps,
uint64_t  isl 
)

Definition at line 43 of file viewer_client_gis.cpp.

44{
45 {
46 std::stringstream ss;
47 ss << "fr=" << std::to_string(fps) << "\n";
48 conn.write(ss.str());
49
50 // Check we received the expected responses
51 std::string response;
52 char ch;
53 while(true) {
54 auto res = conn.read(&ch, 1);
55 if(res.value() <= 0) {
56 IRSOL_LOG_ERROR("Failed to read header");
57 return false;
58 }
59 if(ch == '\n')
60 break;
61 response += ch;
62 }
63 IRSOL_LOG_INFO("Response for FPS: '{}'", response);
64 }
65 {
66 std::stringstream ss;
67 ss << "isl=" << std::to_string(isl) << "\n";
68 conn.write(ss.str());
69
70 // Check we received the expected responses
71 std::string response;
72 char ch;
73 while(true) {
74 auto res = conn.read(&ch, 1);
75 if(res.value() <= 0) {
76 IRSOL_LOG_ERROR("Failed to read header");
77 return false;
78 }
79 if(ch == '\n')
80 break;
81 response += ch;
82 }
83 std::string expectedIslResponse = "isl=" + std::to_string(isl);
84 if(response != expectedIslResponse) {
86 "Expected response for ISL command is '{}', but got '{}'", expectedIslResponse, response);
87 return false;
88 }
89 IRSOL_LOG_INFO("Response for isl: '{}'", response);
90 }
91 return true;
92}
#define IRSOL_LOG_INFO(...)
Logs an info-level message using the default logger.
Definition logging.hpp:92
#define IRSOL_LOG_ERROR(...)
Logs an error-level message using the default logger.
Definition logging.hpp:94

◆ createConnectionWithRetry()

std::optional< irsol::types::connector_t > createConnectionWithRetry ( const std::string &  host,
irsol::types::port_t  port,
std::chrono::seconds  retryTimeout = std::chrono::seconds(1) 
)

Definition at line 222 of file viewer_client_gis.cpp.

226{
227 std::error_code ec;
228 while(!g_terminate.load()) {
229 irsol::types::connector_t conn({host, port}, ec);
230 if(ec) {
232 "Failed to connect to server at {}:{}: {}, retrying in {} seconds",
233 host,
234 port,
235 ec.message(),
236 retryTimeout.count());
237 std::this_thread::sleep_for(retryTimeout);
238 continue;
239 } else {
240 return std::move(conn);
241 }
242 }
243 return std::nullopt;
244}
#define IRSOL_LOG_WARN(...)
Logs a warning-level message using the default logger.
Definition logging.hpp:93
sockpp::tcp_connector connector_t
Alias for the TCP client connector type.
Definition types.hpp:71
std::atomic< bool > g_terminate

◆ main()

int main ( int  argc,
char **  argv 
)

Definition at line 357 of file viewer_client_gis.cpp.

358{
359 irsol::initLogging("logs/viewer-client-gis.log");
361
362 // Register signal handler
363 std::signal(SIGTERM, signalHandler);
364 std::signal(SIGINT, signalHandler); // Also handle Ctrl+C
365
366 args::ArgumentParser parser("TCP client viewer");
367 args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"});
368 args::ValueFlag<double> listen_fps_flag(parser, "fps", "The FPS at which to listen.", {'f'});
369 args::ValueFlag<uint64_t> isl_flag(
370 parser, "isl", "The input sequence length at which to listen.", {'i'});
371 try {
372 parser.ParseCLI(argc, argv);
373 } catch(args::Help) {
374 IRSOL_LOG_INFO("{0:s}", parser.Help());
375 return 0;
376 } catch(args::ParseError e) {
377 IRSOL_LOG_ERROR("Error parsing command-line arguments: {}\n", e.what(), parser.Help());
378 return 1;
379 } catch(args::ValidationError e) {
380 IRSOL_LOG_ERROR("Error parsing command-line arguments: {}\n", e.what(), parser.Help());
381 return 1;
382 }
383 double listen_fps = 0.5;
384 if(listen_fps_flag) {
385 listen_fps = args::get(listen_fps_flag);
386 }
387 uint64_t isl = 10;
388 if(isl_flag) {
389 isl = args::get(isl_flag);
390 }
391
392 run(listen_fps, isl);
393}
void initAssertHandler()
Initializes the assertion handler system.
Definition assert.cpp:14
void initLogging(const char *fileSinkFilename="logs/irsol.log", std::optional< spdlog::level::level_enum > minLogLevel=std::nullopt)
Initializes the irsol logging system.
Definition logging.cpp:108
void run(double inFps, uint64_t sequenceLength)
void signalHandler(int signal)

◆ queryImages()

std::optional< std::vector< std::pair< size_t, cv::Mat > > > queryImages ( irsol::types::connector_t conn)

Definition at line 95 of file viewer_client_gis.cpp.

96{
97 auto t0 = irsol::types::clock_t::now();
98 std::vector<irsol::types::timepoint_t> retrieveTimes;
99
100 conn.write("gis\n");
101
102 std::vector<std::pair<size_t, cv::Mat>> result;
103
104 while(true) {
105 std::string headerTitle;
106 char ch;
107 while(true) {
108 auto res = conn.read(&ch, 1);
109 if(res.value() <= 0) {
110 IRSOL_LOG_ERROR("Failed to read header title");
111 return std::nullopt;
112 }
113 if(ch == '\n' || ch == '=')
114 break;
115 headerTitle += ch;
116 }
117
118 if(headerTitle == "gis;") {
119 IRSOL_LOG_INFO("Received completion signal from server");
120 break; // Done
121 } else if(headerTitle == "isn") {
122 std::string isnStr;
123 while(ch != '\n') {
124 conn.read(&ch, 1);
125 isnStr.insert(isnStr.end(), ch);
126 }
127 uint64_t isn = std::stoull(isnStr);
128 IRSOL_LOG_INFO("Received input sequence number {}", isn);
129 } else if(headerTitle == "img") {
130 IRSOL_LOG_INFO("Received 'img' header");
131
132 // --- Parse image metadata ---
133 uint32_t imageHeight{0};
134 uint32_t imageWidth{0};
135
136 // Wait for SOH (start of header) byte
137 while(true) {
138 auto res = conn.read(&ch, 1);
139 if(res.value() <= 0) {
140 IRSOL_LOG_ERROR("Failed to read image metadata");
141 return std::nullopt;
142 }
144 break;
145 }
146 }
147
148 // Parse image shape: [height,width]
149 while(ch != '[') {
150 conn.read(&ch, 1);
151 }
152 std::string heightStr;
153 while(ch != ',') {
154 conn.read(&ch, 1);
155 heightStr.insert(heightStr.end(), ch);
156 }
157 imageHeight = std::stol(heightStr);
158
159 std::string widthStr;
160 while(ch != ']') {
161 conn.read(&ch, 1);
162 widthStr.insert(widthStr.end(), ch);
163 }
164 imageWidth = std::stol(widthStr);
165
166 // Skip image attributes until STX (start of text) byte
167 while(ch !=
169 conn.read(&ch, 1);
170 }
171
172 // --- Read image data ---
173 uint64_t expectedSize = imageHeight * imageWidth * 2; // 2 bytes per pixel
174 std::vector<uint8_t> buffer(expectedSize);
175
176 size_t totalRead = 0;
177 while(totalRead < expectedSize) {
178 auto res = conn.read(buffer.data() + totalRead, expectedSize - totalRead);
179 if(res.value() <= 0) {
180 IRSOL_LOG_ERROR("Failed to read image data (read {} of {})", totalRead, expectedSize);
181 return std::nullopt;
182 }
183 totalRead += res.value();
184 }
185
186 retrieveTimes.push_back(irsol::types::clock_t::now());
187 cv::Mat img =
188 irsol::opencv::createCvMatFromIrsolServerBuffer(buffer.data(), imageHeight, imageWidth);
189 result.emplace_back(result.size(), std::move(img));
190 } else {
191 IRSOL_LOG_WARN("Skipping unknown header: {}", headerTitle);
192 // Skip the rest of the line
193 while(ch != '\n') {
194 auto res = conn.read(&ch, 1);
195 if(res.value() <= 0)
196 return std::nullopt;
197 }
198 }
199 }
200
201 auto t1 = irsol::types::clock_t::now();
202 auto totalDuration = t1 - t0;
204 "Took {} to retrieve {} frames -> fps {}",
205 irsol::utils::durationToString(totalDuration),
206 result.size(),
207 1000000.0 * result.size() /
208 std::chrono::duration_cast<std::chrono::microseconds>(totalDuration).count());
209 for(size_t i = 0; i < retrieveTimes.size() - 1; ++i) {
210 auto start = retrieveTimes[i];
211 auto end = retrieveTimes[i + 1];
212 auto dt = end - start;
214 "Dts: {} -> fps: {}",
216 1000000.0 / std::chrono::duration_cast<std::chrono::microseconds>(dt).count());
217 }
218 return result;
219}
cv::Mat createCvMatFromIrsolServerBuffer(unsigned char *data, size_t rows, size_t cols)
Creates an OpenCV cv::Mat from a raw buffer received from the IRSOL server.
Definition opencv.cpp:68
std::string bytesToString(const std::vector< irsol::types::byte_t > &input)
Converts a std::vector of irsol::types::byte_t to a std::string.
Definition utils.cpp:189
std::string durationToString(irsol::types::duration_t dr)
Converts a duration to a human-readable string.
Definition utils.cpp:133
static constexpr irsol::types::byte_t SOH
Start of Header (SOH) byte: 0x01.
static constexpr irsol::types::byte_t STX
Start of Text (STX) byte: 0x02.
auto result

◆ run()

void run ( double  inFps,
uint64_t  sequenceLength 
)

Definition at line 247 of file viewer_client_gis.cpp.

248{
249
250 IRSOL_LOG_INFO("TCP client viewer (gis)");
251
252 std::string server_host = "localhost";
253 irsol::types::port_t port = 15099; // port used by existing clients
254
255 sockpp::initialize();
256
258 if(auto connOpt = createConnectionWithRetry(server_host, port); !connOpt.has_value()) {
259 return;
260 } else {
261 conn = std::move(connOpt.value());
262 }
263
264 IRSOL_LOG_INFO("Connected to server");
265
266 // Set a timeout for the responses
267 if(auto res = conn.read_timeout(std::chrono::seconds(10)); !res) {
268 IRSOL_LOG_ERROR("Error setting TCP read timeout: {}", res.error_message());
269 } else {
270 IRSOL_LOG_DEBUG("Read timeout set to 10 seconds");
271 }
272
273 if(!configureGis(conn, inFps, sequenceLength)) {
274 std::exit(1);
275 }
276
277 bool firstFrame = true;
278
279 // Start listening for incoming images
280
281 IRSOL_LOG_INFO("Starting frame polling with FPS: {}", inFps);
282
283 while(!g_terminate.load()) {
284 // --- Query image ---
285 auto imagesOpt = queryImages(conn);
286
287 if(!imagesOpt.has_value()) {
288 IRSOL_LOG_ERROR("Failed to receive image, waiting 1 second..");
289 std::this_thread::sleep_for(std::chrono::seconds(1));
290 continue;
291 }
292
293 auto images = std::move(imagesOpt.value());
294 size_t imageIndex = 0;
295 for(const auto& [imageId, image] : images) {
296
297 if(image.empty()) {
298 IRSOL_LOG_INFO("Received empty image, waiting 1 second..");
299 std::this_thread::sleep_for(std::chrono::seconds(1));
300 continue;
301 }
302
303 // --- Annotate ---
304 if(firstFrame) {
306 "Image {} received: {}x{} with {} channels",
307 imageId,
308 image.rows,
309 image.cols,
310 image.channels());
311 firstFrame = false;
312 }
313
314 cv::putText(
315 image,
316 "ImageID: " + std::to_string(imageId),
317 {20, 80},
318 cv::FONT_HERSHEY_COMPLEX,
319 1.0,
323 1,
324 cv::LINE_AA);
325
326 cv::putText(
327 image,
328 "ImageIndex: " + std::to_string(imageIndex),
329 {20, 140},
330 cv::FONT_HERSHEY_COMPLEX,
331 1.0,
335 1,
336 cv::LINE_AA);
337
338 // --- Display ---
339 cv::imshow("Viewer", image);
340 int key = cv::waitKey(300) & 0xFF;
341 if(key == 27 || key == 'q') {
342 IRSOL_LOG_INFO("Exit requested via keyboard");
343 g_terminate.store(true);
344 }
345
346 ++imageIndex;
347 }
348
349 IRSOL_LOG_INFO("Sleeping prior to sending new gis request");
350 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
351 }
352
353 IRSOL_LOG_INFO("Exiting viewer");
354}
#define IRSOL_LOG_DEBUG(...)
Logs a debug-level message using the default logger.
Definition logging.hpp:91
uint16_t port_t
Represents a network port number. Typically used to specify TCP or UDP ports.
Definition types.hpp:48
static constexpr uint64_t max()
Returns the maximum representable pixel value for this bit depth.
std::optional< irsol::types::connector_t > createConnectionWithRetry(const std::string &host, irsol::types::port_t port, std::chrono::seconds retryTimeout=std::chrono::seconds(1))
bool configureGis(irsol::types::connector_t &conn, double fps, uint64_t isl)
std::optional< std::vector< std::pair< size_t, cv::Mat > > > queryImages(irsol::types::connector_t &conn)

◆ signalHandler()

void signalHandler ( int  signal)

Definition at line 34 of file viewer_client_gis.cpp.

35{
36 if(signal == SIGTERM || signal == SIGINT) {
37 IRSOL_LOG_INFO("Signal {} received, shutting down gracefully...", signal);
38 g_terminate = true;
39 }
40}

Variable Documentation

◆ g_terminate

std::atomic<bool> g_terminate {false}

Definition at line 30 of file viewer_client_gis.cpp.

30{false};