Instrument Neutral Distributed Interface INDI 2.2.5
instrumentation.cpp
Go to the documentation of this file.
1/*
2 Gemini Power Box Hub Advanced v3 INDI Driver
3
4 Copyright (C) 2026 Dieter R Kedrowitsch <[email protected]>
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU General Public License along with
17 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
18 Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 The full GNU General Public License is included in this distribution in the
21 file called LICENSE.
22*/
23
25
26#include <algorithm>
27#include <exception>
28#include <iomanip>
29#include <sstream>
30#include <utility>
31
33{
34
36{
41
44 bool active = true;
45 unsigned activeCalls = 0;
46 std::condition_variable cv;
47};
48
49namespace
50{
51thread_local const Dispatcher *currentDispatcher = nullptr;
52thread_local Dispatcher::ListenerHandle currentListenerHandle = 0;
53
54bool levelAllows(Level configured, Level eventLevel)
55{
56 return eventLevel != Level::Off &&
57 configured != Level::Off &&
58 static_cast<int>(configured) >= static_cast<int>(eventLevel);
59}
60}
61
62Dispatcher::Dispatcher() = default;
63
65{
66 std::unique_lock<std::mutex> dispatchLock(dispatchMutex_);
67 dispatchCv_.wait(dispatchLock, [&] { return !dispatchInProgress_; });
68 dispatchLock.unlock();
69
70 std::lock_guard<std::mutex> listenerLock(listenerMutex_);
71 for (const auto &entry : listeners_) entry->active = false;
72 listeners_.clear();
73 listenerCount_.store(0, std::memory_order_release);
74}
75
77{
78 level_.store(level, std::memory_order_release);
79}
80
82{
83 return level_.load(std::memory_order_acquire);
84}
85
86bool Dispatcher::enabled(Level eventLevel) const
87{
88 return listenerCount_.load(std::memory_order_acquire) != 0 &&
89 levelAllows(level_.load(std::memory_order_acquire), eventLevel);
90}
91
93{
94 if (!listener) return 0;
95
96 std::lock_guard<std::mutex> lock(listenerMutex_);
97 ListenerHandle handle = nextListenerHandle_++;
98 if (nextListenerHandle_ == 0) ++nextListenerHandle_;
99 listeners_.push_back(std::make_shared<ListenerEntry>(handle, std::move(listener)));
100 listenerCount_.store(listeners_.size(), std::memory_order_release);
101 return handle;
102}
103
105{
106 if (handle == 0) return false;
107
108 std::shared_ptr<ListenerEntry> entry;
109 std::unique_lock<std::mutex> lock(listenerMutex_);
110 auto it = std::find_if(listeners_.begin(), listeners_.end(),
111 [handle](const std::shared_ptr<ListenerEntry> &candidate)
112 {
113 return candidate->handle == handle;
114 });
115 if (it == listeners_.end()) return false;
116
117 entry = *it;
118 entry->active = false;
119 listeners_.erase(it);
120 listenerCount_.store(listeners_.size(), std::memory_order_release);
121
122 if (currentDispatcher == this && currentListenerHandle == handle) return true;
123
124 entry->cv.wait(lock, [&] { return entry->activeCalls == 0; });
125 return true;
126}
127
128void Dispatcher::beginDispatch()
129{
130 std::unique_lock<std::mutex> lock(dispatchMutex_);
131 dispatchCv_.wait(lock, [&] { return !dispatchInProgress_; });
132 dispatchInProgress_ = true;
133}
134
135void Dispatcher::endDispatch()
136{
137 {
138 std::lock_guard<std::mutex> lock(dispatchMutex_);
139 dispatchInProgress_ = false;
140 }
141 dispatchCv_.notify_all();
142}
143
144void Dispatcher::emit(Event event) noexcept
145{
146 if (!enabled(event.level)) return;
147
148 try
149 {
150 beginDispatch();
151 }
152 catch (...)
153 {
154 return;
155 }
156
157 struct DispatchGuard
158 {
159 Dispatcher *dispatcher;
160 ~DispatchGuard()
161 {
162 dispatcher->endDispatch();
163 }
164 } guard{this};
165
166 try
167 {
168 std::vector<std::shared_ptr<ListenerEntry>> entries;
169 {
170 std::lock_guard<std::mutex> lock(listenerMutex_);
171 entries = listeners_;
172 }
173
174 for (const auto &entry : entries)
175 {
176 Listener listener;
177 {
178 std::lock_guard<std::mutex> lock(listenerMutex_);
179 if (!entry->active) continue;
180 listener = entry->listener;
181 ++entry->activeCalls;
182 }
183
184 currentDispatcher = this;
185 currentListenerHandle = entry->handle;
186 try
187 {
188 listener(event);
189 }
190 catch (...)
191 {
192 }
193 currentListenerHandle = 0;
194 currentDispatcher = nullptr;
195
196 {
197 std::lock_guard<std::mutex> lock(listenerMutex_);
198 --entry->activeCalls;
199 if (entry->activeCalls == 0) entry->cv.notify_all();
200 }
201 }
202 }
203 catch (...)
204 {
205 }
206}
207
209{
210 Event event;
211 event.level = level;
212 event.type = type;
213 event.wallTime = std::chrono::system_clock::now();
214 event.steadyTime = std::chrono::steady_clock::now();
215 event.payload = std::move(payload);
216 return event;
217}
218
219const char *toString(Level level)
220{
221 switch (level)
222 {
223 case Level::Off:
224 return "off";
225 case Level::Error:
226 return "error";
227 case Level::Info:
228 return "info";
229 case Level::Protocol:
230 return "protocol";
231 case Level::Verbose:
232 return "verbose";
233 case Level::Trace:
234 return "trace";
235 }
236 return "unknown";
237}
238
240{
241 switch (type)
242 {
244 return "connection";
246 return "frame_transmitted";
248 return "frame_received";
250 return "status_decoded";
252 return "field_changes";
254 return "verification";
256 return "warning";
258 return "parser_anomaly";
260 return "reconnect_attempt";
262 return "timing";
263 }
264 return "unknown";
265}
266
267const char *toString(ConnectionState state)
268{
269 switch (state)
270 {
272 return "connected";
274 return "disconnected";
276 return "communication_failure";
278 return "reconnect_success";
279 }
280 return "unknown";
281}
282
283const char *toString(FrameDirection direction)
284{
285 switch (direction)
286 {
288 return "TX";
290 return "RX";
291 }
292 return "unknown";
293}
294
295const char *toString(VerificationOperation operation)
296{
297 switch (operation)
298 {
300 return "command_state_confirm";
301 }
302 return "unknown";
303}
304
305const char *toString(VerificationOutcome outcome)
306{
307 switch (outcome)
308 {
310 return "passed";
312 return "failed";
313 }
314 return "unknown";
315}
316
317const char *toString(WarningCode code)
318{
319 switch (code)
320 {
322 return "firmware_below_recommended";
324 return "queue_full";
326 return "writes_not_accepted";
328 return "queued_commands_discarded";
330 return "callback_exception";
332 return "transport_error";
334 return "serial_write_failed";
336 return "protocol_rejected";
337 }
338 return "unknown";
339}
340
342{
343 switch (code)
344 {
346 return "malformed_status_frame";
348 return "ignored_frame_while_waiting";
350 return "unexpected_frame";
352 return "oversized_frame_discarded";
354 return "missing_terminator";
356 return "trailing_data";
358 return "invalid_field";
360 return "out_of_range_field";
361 }
362 return "unknown";
363}
364
365const char *toString(ReconnectResult result)
366{
367 switch (result)
368 {
370 return "starting";
372 return "attempt_failed";
374 return "succeeded";
376 return "cancelled";
378 return "exhausted";
379 }
380 return "unknown";
381}
382
383const char *toString(TimingOperation operation)
384{
385 switch (operation)
386 {
388 return "open";
390 return "handshake";
392 return "read_firmware";
394 return "first_status_frame";
396 return "connect";
398 return "reconnect_attempt";
400 return "sync_write";
402 return "async_write";
404 return "parse_status_payload";
405 }
406 return "unknown";
407}
408
409std::string formatValue(const Value &value)
410{
411 struct Visitor
412 {
413 std::string operator()(std::monostate) const
414 {
415 return "null";
416 }
417 std::string operator()(bool value) const
418 {
419 return value ? "true" : "false";
420 }
421 std::string operator()(std::int64_t value) const
422 {
423 return std::to_string(value);
424 }
425 std::string operator()(std::uint64_t value) const
426 {
427 return std::to_string(value);
428 }
429 std::string operator()(double value) const
430 {
431 std::ostringstream oss;
432 oss << value;
433 return oss.str();
434 }
435 std::string operator()(const std::string &value) const
436 {
437 return value;
438 }
439 };
440 return std::visit(Visitor{}, value);
441}
442
443std::string escapeFrame(const std::string &frame)
444{
445 std::ostringstream oss;
446 for (char c : frame)
447 {
448 unsigned char byte = static_cast<unsigned char>(c);
449 if (c == '\r') oss << "<CR>";
450 else if (c == '\n') oss << "<LF>";
451 else if (c == '#') oss << "<HASH>";
452 else if (byte < 0x20
453 || byte >= 0x7f) oss << "<0x" << std::hex << std::uppercase << static_cast<int>(byte) << ">" << std::dec;
454 else oss << c;
455 }
456 return oss.str();
457}
458
459namespace
460{
461std::string formatNamedValues(const std::vector<NamedValue> &fields)
462{
463 std::ostringstream oss;
464 for (std::size_t i = 0; i < fields.size(); ++i)
465 {
466 if (i > 0) oss << ", ";
467 oss << fields[i].name << '=' << formatValue(fields[i].value);
468 }
469 return oss.str();
470}
471
472std::string formatFieldChanges(const std::vector<FieldChange> &changes)
473{
474 std::ostringstream oss;
475 for (std::size_t i = 0; i < changes.size(); ++i)
476 {
477 if (i > 0) oss << ", ";
478 oss << changes[i].name << '=' << formatValue(changes[i].previous)
479 << "->" << formatValue(changes[i].current);
480 }
481 return oss.str();
482}
483}
484
485std::string EventFormatter::format(const Event &event) const
486{
487 return std::visit([&](const auto & payload) -> std::string
488 {
489 using PayloadType = std::decay_t<decltype(payload)>;
490 if constexpr (std::is_same<PayloadType, ConnectionPayload>::value)
491 {
492 if (payload.state == ConnectionState::CommunicationFailure && !payload.reason.empty())
493 {
494 return "communication failure: " + payload.reason;
495 }
496 std::string out = std::string("connection: ") + toString(payload.state);
497 if (!payload.reason.empty()) out += ": " + payload.reason;
498 return out;
499 }
500 else if constexpr (std::is_same<PayloadType, FramePayload>::value)
501 {
502 return std::string(toString(payload.direction)) + ": " + escapeFrame(payload.frame);
503 }
504 else if constexpr (std::is_same<PayloadType, StatusDecodedPayload>::value)
505 {
506 std::string out = "status decoded";
507 if (payload.sequence != 0) out += " seq=" + std::to_string(payload.sequence);
508 if (!payload.frameType.empty()) out += " type=" + payload.frameType;
509 if (!payload.fields.empty()) out += ": " + formatNamedValues(payload.fields);
510 return out;
511 }
512 else if constexpr (std::is_same<PayloadType, FieldChangesPayload>::value)
513 {
514 std::string out = "field changes";
515 if (payload.sequence != 0) out += " seq=" + std::to_string(payload.sequence);
516 if (!payload.changes.empty()) out += ": " + formatFieldChanges(payload.changes);
517 return out;
518 }
519 else if constexpr (std::is_same<PayloadType, VerificationPayload>::value)
520 {
521 std::string out = std::string("verification ") + toString(payload.outcome) + " " + toString(payload.operation);
522 if (payload.relatedSequence != 0) out += " seq=" + std::to_string(payload.relatedSequence);
523 if (!payload.detail.empty()) out += ": " + payload.detail;
524 return out;
525 }
526 else if constexpr (std::is_same<PayloadType, WarningPayload>::value)
527 {
528 if (!payload.message.empty()) return payload.message;
529 return std::string("warning: ") + toString(payload.code);
530 }
531 else if constexpr (std::is_same<PayloadType, ParserAnomalyPayload>::value)
532 {
533 std::string out = std::string("parser anomaly ") + toString(payload.code);
534 if (!payload.detail.empty()) out += ": " + payload.detail;
535 if (!payload.frame.empty()) out += " (raw: " + escapeFrame(payload.frame) + ")";
536 return out;
537 }
538 else if constexpr (std::is_same<PayloadType, ReconnectAttemptPayload>::value)
539 {
540 if (payload.result == ReconnectResult::Exhausted)
541 {
542 return "reconnect: exhausted retries, exiting";
543 }
544 if (payload.result == ReconnectResult::Starting && payload.attempt != 0 && payload.maxAttempts != 0)
545 {
546 return "reconnect: attempt " + std::to_string(payload.attempt) + "/" + std::to_string(payload.maxAttempts);
547 }
548 std::string out = "reconnect: ";
549 if (payload.attempt != 0 && payload.maxAttempts != 0)
550 out += "attempt " + std::to_string(payload.attempt) + "/" + std::to_string(payload.maxAttempts) + " ";
551 out += toString(payload.result);
552 if (payload.delayBeforeAttempt.count() > 0)
553 out += " after " + std::to_string(payload.delayBeforeAttempt.count()) + "ms";
554 return out;
555 }
556 else if constexpr (std::is_same<PayloadType, TimingPayload>::value)
557 {
558 return std::string("timing: ") + toString(payload.operation) + " " +
559 (payload.success ? "ok" : "failed") + " " +
560 std::to_string(payload.duration.count()) + "us";
561 }
562 return std::string(toString(event.type));
563 }, event.payload);
564}
565
566EventLogger::EventLogger(Sink sink) : sink_(std::move(sink)) {}
567
569{
570 sink_ = std::move(sink);
571}
572
573void EventLogger::operator()(const Event &event) const noexcept
574{
575 if (!sink_) return;
576 try
577 {
578 sink_(event.level, formatter_.format(event));
579 }
580 catch (...)
581 {
582 }
583}
584
585} // namespace geminipbh::instrumentation
ListenerHandle registerListener(Listener listener)
bool removeListener(ListenerHandle handle)
std::function< void(const Event &)> Listener
bool enabled(Level eventLevel) const
std::string format(const Event &event) const
std::function< void(Level, const std::string &)> Sink
void operator()(const Event &event) const noexcept
const char * toString(Level level)
std::string escapeFrame(const std::string &frame)
std::string formatValue(const Value &value)
std::variant< ConnectionPayload, FramePayload, StatusDecodedPayload, FieldChangesPayload, VerificationPayload, WarningPayload, ParserAnomalyPayload, ReconnectAttemptPayload, TimingPayload > Payload
Event makeEvent(Level level, EventType type, Payload payload)
std::variant< std::monostate, bool, std::int64_t, std::uint64_t, double, std::string > Value
uint16_t type
Definition pwc-ioctl.h:0
ListenerEntry(ListenerHandle handle, Listener listener)