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

Example client that repeatedly sends gi (get image) commands to the camera server. More...

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

Go to the source code of this file.

Functions

void signalHandler (int signal)
 
std::optional< std::pair< size_t, cv::Mat > > queryImage (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)
 
int main (int argc, char **argv)
 

Variables

std::atomic< bool > g_terminate {false}
 

Detailed Description

Example client that repeatedly sends gi (get image) commands to the camera server.

This executable connects to the camera server and requests single image frames using the gi command. Each received image is displayed using OpenCV, and the measured FPS is annotated on the image.

Command-line options: -f, –fps <fps> Set the polling frequency (default: 0.5 FPS)

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

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

Definition in file viewer_client_gi.cpp.

Function Documentation

◆ 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 142 of file viewer_client_gi.cpp.

146{
147 std::error_code ec;
148 while(!g_terminate.load()) {
149 irsol::types::connector_t conn({host, port}, ec);
150 if(ec) {
152 "Failed to connect to server at {}:{}: {}, retrying in {} seconds",
153 host,
154 port,
155 ec.message(),
156 retryTimeout.count());
157 std::this_thread::sleep_for(retryTimeout);
158 continue;
159 } else {
160 return std::move(conn);
161 }
162 }
163 return std::nullopt;
164}
#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 284 of file viewer_client_gi.cpp.

285{
286 irsol::initLogging("logs/viewer-client-gi.log");
288
289 // Register signal handler
290 std::signal(SIGTERM, signalHandler);
291 std::signal(SIGINT, signalHandler); // Also handle Ctrl+C
292
293 args::ArgumentParser parser("TCP client viewer");
294 args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"});
295 args::ValueFlag<double> listen_fps_flag(parser, "fps", "The FPS at which to listen.", {'f'});
296 try {
297 parser.ParseCLI(argc, argv);
298 } catch(args::Help) {
299 IRSOL_LOG_INFO("{0:s}", parser.Help());
300 return 0;
301 } catch(args::ParseError e) {
302 IRSOL_LOG_ERROR("Error parsing command-line arguments: {}\n", e.what(), parser.Help());
303 return 1;
304 } catch(args::ValidationError e) {
305 IRSOL_LOG_ERROR("Error parsing command-line arguments: {}\n", e.what(), parser.Help());
306 return 1;
307 }
308 double listen_fps = 0.5;
309 if(listen_fps_flag) {
310 listen_fps = args::get(listen_fps_flag);
311 }
312
313 run(listen_fps);
314}
void initAssertHandler()
Initializes the assertion handler system.
Definition assert.cpp:14
#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
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)
void signalHandler(int signal)

◆ queryImage()

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

Definition at line 41 of file viewer_client_gi.cpp.

42{
43 conn.write("gi\n");
44
45 // Loop until we get the correct header start
46 while(true) {
47 std::string headerTitle;
48 char ch;
49 while(true) {
50 auto res = conn.read(&ch, 1);
51 if(res.value() <= 0)
52 return std::nullopt;
53 if(ch == '\n' || ch == '=')
54 break;
55 headerTitle += ch;
56 }
57
58 if(headerTitle == "gi;") {
59 IRSOL_LOG_INFO("Received success confirmation from server");
60 } else if(headerTitle == "isn") {
61 std::string isnStr;
62 while(ch != '\n') {
63 conn.read(&ch, 1);
64 isnStr.insert(isnStr.end(), ch);
65 }
66 uint64_t isn = std::stoull(isnStr);
67 IRSOL_LOG_INFO("Received input sequence number {}", isn);
68 } else if(headerTitle == "img") {
69 IRSOL_LOG_INFO("Received 'img' header");
70
71 // --- Parse image metadata ---
72 uint32_t imageHeight{0};
73 uint32_t imageWidth{0};
74
75 // Wait for SOH (start of header) byte
76 while(true) {
77 auto res = conn.read(&ch, 1);
78 if(res.value() <= 0) {
79 IRSOL_LOG_ERROR("Failed to read image metadata");
80 return std::nullopt;
81 }
83 break;
84 }
85 }
86
87 // Parse image shape: [height,width]
88 while(ch != '[') {
89 conn.read(&ch, 1);
90 }
91 std::string heightStr;
92 while(ch != ',') {
93 conn.read(&ch, 1);
94 heightStr.insert(heightStr.end(), ch);
95 }
96 imageHeight = std::stol(heightStr);
97
98 std::string widthStr;
99 while(ch != ']') {
100 conn.read(&ch, 1);
101 widthStr.insert(widthStr.end(), ch);
102 }
103 imageWidth = std::stol(widthStr);
104
105 // Skip image attributes until STX (start of text) byte
106 while(ch !=
108 conn.read(&ch, 1);
109 }
110
111 // --- Read image data ---
112 uint64_t expectedSize = imageHeight * imageWidth * 2; // 2 bytes per pixel
113 std::vector<uint8_t> buffer(expectedSize);
114
115 size_t totalRead = 0;
116 while(totalRead < expectedSize) {
117 auto res = conn.read(buffer.data() + totalRead, expectedSize - totalRead);
118 if(res.value() <= 0) {
119 IRSOL_LOG_ERROR("Error reading image data");
120 return std::nullopt;
121 }
122 totalRead += res.value();
123 }
124
125 // --- Convert image data to OpenCV Mat ---
126 return std::make_pair(
127 1, irsol::opencv::createCvMatFromIrsolServerBuffer(buffer.data(), imageHeight, imageWidth));
128 } else {
129
130 IRSOL_LOG_WARN("Skipping unknown header: '{}'", headerTitle);
131 // Skip the rest of the line
132 while(ch != '\n') {
133 auto res = conn.read(&ch, 1);
134 if(res.value() <= 0)
135 return std::nullopt;
136 }
137 }
138 }
139}
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
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.

◆ run()

void run ( double  inFps)

Definition at line 167 of file viewer_client_gi.cpp.

168{
169
170 IRSOL_LOG_INFO("TCP client viewer (gi)");
171
172 std::string server_host = "localhost";
173 irsol::types::port_t port = 15099; // port used by existing clients
174
175 sockpp::initialize();
176
178 if(auto connOpt = createConnectionWithRetry(server_host, port); !connOpt.has_value()) {
179 return;
180 } else {
181 conn = std::move(connOpt.value());
182 }
183
184 IRSOL_LOG_INFO("Connected to server");
185
186 // Set a timeout for the responses
187 if(auto res = conn.read_timeout(std::chrono::seconds(10)); !res) {
188 IRSOL_LOG_ERROR("Error setting TCP read timeout: {}", res.error_message());
189 } else {
190 IRSOL_LOG_DEBUG("Read timeout set to 10 seconds");
191 }
192
193 auto lastTimeShown = irsol::types::clock_t::now() - std::chrono::seconds(1000);
194
195 bool firstFrame = true;
196
197 // Start listening for incoming images
198
199 IRSOL_LOG_INFO("Starting frame polling with FPS: {}", inFps);
200
201 while(!g_terminate.load()) {
202 auto frameStart = irsol::types::clock_t::now();
203
204 // --- Query image ---
205 auto imageOpt = queryImage(conn);
206
207 if(!imageOpt.has_value()) {
208 IRSOL_LOG_ERROR("Failed to receive image, waiting 1 second..");
209 std::this_thread::sleep_for(std::chrono::seconds(1));
210 continue;
211 }
212
213 auto [imageId, image] = std::move(imageOpt.value());
214 if(image.empty()) {
215 IRSOL_LOG_INFO("Received empty image, waiting 1 second..");
216 std::this_thread::sleep_for(std::chrono::seconds(1));
217 continue;
218 }
219
220 // --- Annotate ---
221 if(firstFrame) {
223 "Image {} received: {}x{} with {} channels",
224 imageId,
225 image.rows,
226 image.cols,
227 image.channels());
228 firstFrame = false;
229 }
230
231 cv::putText(
232 image,
233 "ImageID: " + std::to_string(imageId),
234 {20, 80},
235 cv::FONT_HERSHEY_COMPLEX,
236 1.0,
240 1,
241 cv::LINE_AA);
242
243 // Show current measured FPS
244 auto frameNow = irsol::types::clock_t::now();
245 double actualFps = 1.0 / std::chrono::duration<double>(frameNow - lastTimeShown).count();
246 lastTimeShown = frameNow;
247
248 IRSOL_LOG_DEBUG("Measured FPS: {:.2f}", actualFps);
249
250 cv::putText(
251 image,
252 "Measured FPS: " + std::to_string(actualFps),
253 {20, 160},
254 cv::FONT_HERSHEY_COMPLEX,
255 1.0,
259 1,
260 cv::LINE_AA);
261
262 // --- Display ---
263 cv::imshow("Viewer", image);
264 int key = cv::waitKey(1) & 0xFF;
265 if(key == 27 || key == 'q') {
266 IRSOL_LOG_INFO("Exit requested via keyboard");
267 g_terminate.store(true);
268 }
269
270 // --- Frame duration regulation ---
271 auto frameEnd = irsol::types::clock_t::now();
272 auto frameDuration = frameEnd - frameStart;
273
274 auto desiredFrameTime = std::chrono::microseconds(static_cast<int64_t>(1'000'000.0 / inFps));
275 if(frameDuration < desiredFrameTime) {
276 std::this_thread::sleep_for(desiredFrameTime - frameDuration);
277 }
278 }
279
280 IRSOL_LOG_INFO("Exiting viewer");
281}
#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))
std::optional< std::pair< size_t, cv::Mat > > queryImage(irsol::types::connector_t &conn)

◆ signalHandler()

void signalHandler ( int  signal)

Definition at line 32 of file viewer_client_gi.cpp.

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

Variable Documentation

◆ g_terminate

std::atomic<bool> g_terminate {false}

Definition at line 28 of file viewer_client_gi.cpp.

28{false};