IRSOL
C++ code implementing socket server for interacting with Baumer camera.
viewer_client_gi.cpp
Go to the documentation of this file.
1
19#include "irsol/args.hpp"
20#include "irsol/irsol.hpp"
21
22#include <atomic>
23#include <csignal>
24#include <opencv2/opencv.hpp>
25#include <optional>
26
27// Global flag to indicate shutdown request
28std::atomic<bool> g_terminate{false};
29
30// Signal handler function
31void
32signalHandler(int signal)
33{
34 if(signal == SIGTERM || signal == SIGINT) {
35 IRSOL_LOG_INFO("Signal {} received, shutting down gracefully...", signal);
36 g_terminate = true;
37 }
38}
39
40std::optional<std::pair<size_t, cv::Mat>>
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}
140
141std::optional<irsol::types::connector_t>
143 const std::string& host,
145 std::chrono::seconds retryTimeout = std::chrono::seconds(1))
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}
165
166void
167run(double inFps)
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}
282
283int
284main(int argc, char** argv)
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}
int main()
Definition main.cpp:90
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
#define IRSOL_LOG_WARN(...)
Logs a warning-level message using the default logger.
Definition logging.hpp:93
#define IRSOL_LOG_DEBUG(...)
Logs a debug-level message using the default logger.
Definition logging.hpp:91
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
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
uint16_t port_t
Represents a network port number. Typically used to specify TCP or UDP ports.
Definition types.hpp:48
sockpp::tcp_connector connector_t
Alias for the TCP client connector type.
Definition types.hpp:71
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 uint64_t max()
Returns the maximum representable pixel value for this bit depth.
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.
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)
void run(double inFps)
std::atomic< bool > g_terminate
void signalHandler(int signal)