diff options
| author | Samuel Hunt <sam@maxxwave.co.uk> | 2026-01-12 16:49:02 +0000 |
|---|---|---|
| committer | Samuel Hunt <sam@maxxwave.co.uk> | 2026-01-12 16:49:02 +0000 |
| commit | eb677c4548005b4e6423749aed8ee86386e9ba57 (patch) | |
| tree | 57f7178681782694a1e003d0bb217bb05b0d178f /src/fig/FIGCarouselPriority.cpp | |
| parent | 21a23aeff958a1d37f6f44797382f18cbe2a448e (diff) | |
| download | dabmux-eb677c4548005b4e6423749aed8ee86386e9ba57.tar.gz dabmux-eb677c4548005b4e6423749aed8ee86386e9ba57.tar.bz2 dabmux-eb677c4548005b4e6423749aed8ee86386e9ba57.zip | |
Fixed 0/0, empty FIC and improved priority scheduling
Diffstat (limited to 'src/fig/FIGCarouselPriority.cpp')
| -rw-r--r-- | src/fig/FIGCarouselPriority.cpp | 538 |
1 files changed, 358 insertions, 180 deletions
diff --git a/src/fig/FIGCarouselPriority.cpp b/src/fig/FIGCarouselPriority.cpp index fc15c80..6bca635 100644 --- a/src/fig/FIGCarouselPriority.cpp +++ b/src/fig/FIGCarouselPriority.cpp @@ -1,6 +1,6 @@ /* - Copyright (C) 2026 - Samuel Hunt, Maxxwave Ltd. sam@maxxwave.co.uk + Copyright (C) 2024 + Matthias P. Braendli, matthias.braendli@mpb.li Implementation of a priority-based FIG carousel scheduler. */ @@ -27,6 +27,7 @@ #include <algorithm> #include <sstream> +#include <iomanip> #include <limits> #include <cstring> @@ -46,10 +47,10 @@ FIGEntryPriority* PriorityLevel::find_must_send() FIGEntryPriority* PriorityLevel::find_can_send() { - for (auto* entry : carousel) { - // A FIG can send if it has data (even if must_send is false) - // We always have something to send - the FIG will return 0 bytes if nothing - return entry; + // Return front of carousel if non-empty + // Any FIG can potentially send - it will return 0 bytes if nothing to send + if (!carousel.empty()) { + return carousel.front(); } return nullptr; } @@ -142,63 +143,76 @@ FIGCarouselPriority::FIGCarouselPriority( void FIGCarouselPriority::assign_figs_to_priorities() { /* - * Priority assignments: + * Priority assignments based on WorldDAB "Guidance on FIC generation + * for high service count ensembles": + * + * Priority 0 (every frame): 0/0, 0/7 - Fixed insertion (first FIB of every frame) + * Priority 1 (reset 1): 0/1, 0/2 - Core MCI (fixed share, ~50% of FIC) + * Priority 2 (reset 2): 1/1, 1/0 - Service labels + ensemble label (fixed share) + * Critical for scan completion - receivers timeout! + * Priority 3 (reset 4): 0/9 - ECC (timing critical, once/sec) + * Priority 4 (reset 8): 0/3, 0/8, 0/13 - Packet mode, component details + * Priority 5 (reset 16): 0/5, 0/10, 0/17, 0/18, 0/20 - Scaled insertion + * 1/4, 1/5, 2/0, 2/1, 2/4, 2/5 - Other labels (match label cycle) + * PTY (0/17) is NOT essential for scan, just nice-to-have + * Priority 6 (reset 32): 0/14, 0/19 - Announcements, FEC + * Priority 7 (reset 64): 0/6 - Service linking (short form) + * Priority 8 (reset 128): 0/21, 0/24 - Frequency info, OE (best effort, 2 mins) + * Priority 9 (reserved): (future/dynamic use) * - * Priority 0 (every frame): 0/0, 0/7 - Critical MCI - * Priority 1 (reset 2): 0/1, 0/2 - Core MCI (subchannel/service organisation) - * Priority 2 (reset 4): 0/3 - Service component in packet mode - * Priority 3 (reset 8): 1/1 - Programme service labels - * Priority 4 (reset 16): 0/8, 0/13 - Service component global definition, user apps - * Priority 5 (reset 32): 0/5, 0/9, 0/10, 0/17, 0/18 - Metadata - * Priority 6 (reset 64): 1/0, 1/4, 1/5, 2/0, 2/1, 2/4, 2/5 - Labels - * Priority 7 (reset 128): 0/6, 0/14, 0/19 - Service linking, FEC, announcements - * Priority 8 (reset 256): 0/21, 0/24 - Frequency info, OE services - * Priority 9 (reset 512): (reserved for future/dynamic use) + * Key insight from WorldDAB: For >20 services, service labels (1/1) are + * critical for scan completion. Receivers abort scan if labels timeout. + * PTY and other metadata should be "scaled insertion" - match label cycle, + * not compete with it. */ - // Priority 0: Critical (every frame) + // Priority 0: Fixed insertion (every frame at framephase 0) add_fig_to_priority(m_fig0_0, 0); add_fig_to_priority(m_fig0_7, 0); - // Priority 1: Core MCI + // Priority 1: Core MCI (fixed share - ~50% of FIC capacity) add_fig_to_priority(m_fig0_1, 1); add_fig_to_priority(m_fig0_2, 1); - // Priority 2: Packet mode - add_fig_to_priority(m_fig0_3, 2); + // Priority 2: Service labels (fixed share - critical for scan completion!) + // WorldDAB: "labels should get 2 FIGs per 96ms frame" + // Ensemble label should match service label rate + add_fig_to_priority(m_fig1_1, 2); // Programme service labels - PROMOTED + add_fig_to_priority(m_fig1_0, 2); // Ensemble label - PROMOTED - // Priority 3: Service labels - add_fig_to_priority(m_fig1_1, 3); + // Priority 3: ECC (timing critical - once per second) + add_fig_to_priority(m_fig0_9, 3); // Priority 4: Component details - add_fig_to_priority(m_fig0_8, 4); - add_fig_to_priority(m_fig0_13, 4); - - // Priority 5: Metadata - add_fig_to_priority(m_fig0_5, 5); - add_fig_to_priority(m_fig0_9, 5); - add_fig_to_priority(m_fig0_10, 5); - add_fig_to_priority(m_fig0_17, 5); - add_fig_to_priority(m_fig0_18, 5); + add_fig_to_priority(m_fig0_3, 4); // Packet mode + add_fig_to_priority(m_fig0_8, 4); // Service component global def + add_fig_to_priority(m_fig0_13, 4); // User applications + + // Priority 5: Scaled insertion (should match label cycle time) + // These are important but NOT critical for scan completion + add_fig_to_priority(m_fig0_5, 5); // Language + add_fig_to_priority(m_fig0_10, 5); // Date and time + add_fig_to_priority(m_fig0_17, 5); // PTY - DEMOTED (not essential for scan) + add_fig_to_priority(m_fig0_18, 5); // Announcement support add_fig_to_priority(m_fig0_20, 5); // SCI - Service Component Information + // Other labels (component, data service) - less critical than service labels + add_fig_to_priority(m_fig1_4, 5); // Component labels + add_fig_to_priority(m_fig1_5, 5); // Data service labels + add_fig_to_priority(m_fig2_0, 5); // Extended ensemble label + add_fig_to_priority(m_fig2_1, 5); // Extended service labels + add_fig_to_priority(m_fig2_4, 5); // Extended component labels + add_fig_to_priority(m_fig2_5, 5); // Extended data service labels - // Priority 6: Labels (ensemble, component, data, extended) - add_fig_to_priority(m_fig1_0, 6); - add_fig_to_priority(m_fig1_4, 6); - add_fig_to_priority(m_fig1_5, 6); - add_fig_to_priority(m_fig2_0, 6); - add_fig_to_priority(m_fig2_1, 6); - add_fig_to_priority(m_fig2_4, 6); - add_fig_to_priority(m_fig2_5, 6); + // Priority 6: Announcements and FEC + add_fig_to_priority(m_fig0_14, 6); // FEC subchannel organisation + add_fig_to_priority(m_fig0_19, 6); // Announcement switching - // Priority 7: Linking and announcements - add_fig_to_priority(m_fig0_6, 7); - add_fig_to_priority(m_fig0_14, 7); - add_fig_to_priority(m_fig0_19, 7); + // Priority 7: Service linking + add_fig_to_priority(m_fig0_6, 7); // Service linking (short form) - // Priority 8: Frequency/OE - add_fig_to_priority(m_fig0_21, 8); - add_fig_to_priority(m_fig0_24, 8); + // Priority 8: Frequency/OE (best effort - within 2 minutes) + add_fig_to_priority(m_fig0_21, 8); // Frequency information + add_fig_to_priority(m_fig0_24, 8); // OE services // Priority 9: Reserved for dynamic adjustment } @@ -220,63 +234,135 @@ void FIGCarouselPriority::add_fig_to_priority(IFIG& fig, int priority) void FIGCarouselPriority::tick_all_deadlines(int elapsed_ms) { for (auto& entry : m_all_entries) { - entry->tick_deadline(elapsed_ms); + bool start_new = entry->tick_deadline(elapsed_ms); // Check if a new cycle should start - if (!entry->must_send && entry->deadline_ms <= 0) { + if (start_new) { entry->start_new_cycle(); - entry->deadline_ms = entry->rate_ms; // Reset for next cycle } } } void FIGCarouselPriority::check_and_log_deadlines(uint64_t current_frame) { + // Log every ~6 seconds (250 frames at 24ms) if ((current_frame % 250) != 0) { return; } - std::stringstream ss; - bool any_violated = false; + std::stringstream ss_slow; // 1x-2x nominal (acceptable per WorldDAB) + std::stringstream ss_warning; // 2x-3x nominal (approaching limit) + std::stringstream ss_critical; // >3x nominal (unacceptable) + bool any_slow = false; + bool any_warning = false; + bool any_critical = false; for (auto& entry : m_all_entries) { - if (entry->deadline_violated) { - ss << " " << entry->name(); + if (entry->deadline_violated && entry->cycle_count > 0) { + // Check how severe the violation is based on average cycle time + uint64_t avg = entry->avg_cycle_time_ms(); + uint64_t required = entry->rate_ms; + + if (avg > required * 3) { + ss_critical << " " << entry->name(); + any_critical = true; + } else if (avg > required * 2) { + ss_warning << " " << entry->name(); + any_warning = true; + } else if (avg > required) { + ss_slow << " " << entry->name(); + any_slow = true; + } entry->deadline_violated = false; - any_violated = true; } } - if (any_violated) { - etiLog.level(info) << "FIGCarouselPriority: Could not respect repetition rates for FIGs:" << ss.str(); + // Log critical issues first (these violate WorldDAB guidance) + if (any_critical) { + etiLog.level(warn) << "FIGCarouselPriority: UNACCEPTABLE repetition rates (>3x nominal) for FIGs:" << ss_critical.str(); + } + + // Log warnings (approaching limit) + if (any_warning) { + etiLog.level(info) << "FIGCarouselPriority: Slow repetition rates (2x-3x nominal) for FIGs:" << ss_warning.str(); + } + + // Log slow but acceptable (only if no worse issues, to avoid log spam) + if (any_slow && !any_warning && !any_critical) { + etiLog.level(info) << "FIGCarouselPriority: Repetition rates slightly exceeded for FIGs:" << ss_slow.str(); } } -size_t FIGCarouselPriority::write_fibs(uint8_t* buf, uint64_t current_frame, bool fib3_present) +void FIGCarouselPriority::log_rate_statistics(uint64_t current_frame) { - m_rti.currentFrame = current_frame; +#if PRIORITY_RATE_STATS_DEBUG + // Log every ~10 seconds (approx 417 frames) + m_stats_log_counter++; + if (m_stats_log_counter < 417) { + return; + } + m_stats_log_counter = 0; - const int fibCount = fib3_present ? 4 : 3; - const int framephase = current_frame % 4; + etiLog.level(info) << "=== FIG Repetition Rate Statistics ==="; + etiLog.level(info) << "FIG Required Avg Min Max Cycles"; + etiLog.level(info) << "------------ -------- -------- -------- -------- --------"; -#if PRIORITY_CAROUSEL_DEBUG - if ((current_frame % 50) == 0) { // Log every 50 frames (~1.2 sec) - std::stringstream ss; - ss << "Frame " << current_frame << " (phase " << framephase << ") FIG deadlines: "; - for (auto& entry : m_all_entries) { - if (entry->must_send || entry->deadline_ms < 50) { - ss << entry->name() << "(" << entry->deadline_ms << "ms" - << (entry->must_send ? ",MUST" : "") - << (entry->deadline_violated ? ",VIOL" : "") << ") "; + for (auto& entry : m_all_entries) { + if (entry->cycle_count > 0) { + std::stringstream ss; + ss << std::left << std::setw(12) << entry->name() + << " " << std::right << std::setw(7) << entry->rate_ms << "ms" + << " " << std::setw(7) << entry->avg_cycle_time_ms() << "ms" + << " " << std::setw(7) << entry->min_cycle_time_ms << "ms" + << " " << std::setw(7) << entry->max_cycle_time_ms << "ms" + << " " << std::setw(8) << entry->cycle_count; + + // WorldDAB guidance: worst case is 3x nominal rate + // - Within spec: no marker + // - SLOW (1x-2x): exceeds nominal but well within WorldDAB limits + // - [!] (2x-3x): approaching WorldDAB worst-case limit + // - UNACCEPTABLE (>3x): violates WorldDAB guidance + uint64_t avg = entry->avg_cycle_time_ms(); + uint64_t required = entry->rate_ms; + if (avg > required * 3) { + ss << " UNACCEPTABLE"; // >3x nominal - violates WorldDAB + } else if (avg > required * 2) { + ss << " [!]"; // 2x-3x nominal - approaching limit + } else if (avg > required) { + ss << " [SLOW]"; // 1x-2x nominal - exceeds but acceptable } + + etiLog.level(info) << ss.str(); } - etiLog.level(debug) << ss.str(); + + // Reset stats for next interval + entry->reset_stats(); } + etiLog.level(info) << "======================================"; +#else + (void)current_frame; #endif +} + +size_t FIGCarouselPriority::write_fibs(uint8_t* buf, uint64_t current_frame, bool fib3_present) +{ + m_rti.currentFrame = current_frame; + + // Tick all deadline monitors (24ms per frame) + tick_all_deadlines(24); + + // Periodically log missed deadlines + check_and_log_deadlines(current_frame); + + // Periodically log rate statistics (if enabled) + log_rate_statistics(current_frame); + + const int fibCount = fib3_present ? 4 : 3; + const int framephase = current_frame % 4; for (int fib = 0; fib < fibCount; fib++) { memset(buf, 0x00, 30); - size_t figSize = fill_fib(buf, 30, framephase); + size_t figSize = fill_fib(buf, 30, fib, framephase); if (figSize < 30) { buf[figSize] = 0xff; // End marker @@ -297,145 +383,208 @@ size_t FIGCarouselPriority::write_fibs(uint8_t* buf, uint64_t current_frame, boo *(buf++) = crc & 0xFF; } - // Tick all deadline monitors AFTER sending (24ms per frame) - tick_all_deadlines(24); - - // Periodically log missed deadlines - check_and_log_deadlines(current_frame); - return 32 * fibCount; } -size_t FIGCarouselPriority::fill_fib(uint8_t* buf, size_t max_size, int framephase) +size_t FIGCarouselPriority::fill_fib(uint8_t* buf, size_t max_size, int fib_index, int framephase) { size_t written = 0; #if PRIORITY_CAROUSEL_DEBUG std::stringstream fib_log; - fib_log << "FIB fill (phase " << framephase << "): "; + fib_log << "FIB" << fib_index << " fp" << framephase << ": "; #endif - // Step 1: Priority 0 always first (FIG 0/0, 0/7) - size_t p0_written = send_priority_zero(buf, max_size, framephase); - written += p0_written; - -#if PRIORITY_CAROUSEL_DEBUG - if (p0_written > 0) { - fib_log << "P0:" << p0_written << "B "; - } -#endif + // Step 1: Priority 0 - FIG 0/0 and 0/7 (only in FIB 0 at framephase 0) + written += send_priority_zero(buf, max_size, fib_index, framephase); size_t remaining = max_size - written; uint8_t* pbuf = buf + written; - // Step 2: Must-send pass - send FIGs that are due - int attempts = 0; - const int max_attempts = NUM_PRIORITIES * 2; // Prevent infinite loop + // Track which FIGs we've tried this pass to avoid infinite loops + std::unordered_set<FIGEntryPriority*> tried_this_fib; - while (remaining > 2 && attempts < max_attempts) { - attempts++; - - // Find any priority with must_send FIGs - FIGEntryPriority* entry = nullptr; - int send_prio = -1; + // Step 2: Must-send pass - clear all urgent FIGs using cascading priority + // We iterate through priorities and try each must_send FIG. + // A FIG might return 0 bytes if it doesn't fit, so we try all of them. + + bool made_progress = true; + while (remaining > 2 && made_progress) { + made_progress = false; - // First try the selected priority - int prio = select_priority(); - if (prio >= 0) { - entry = m_priorities[prio].find_must_send(); - if (entry) { - send_prio = prio; - } + // Try each priority, starting from the selected one + int start_prio = select_priority(); + if (start_prio < 0) { + start_prio = 1; } - // If selected priority has nothing, search all priorities - if (!entry) { - for (int p = 1; p < NUM_PRIORITIES; p++) { - entry = m_priorities[p].find_must_send(); - if (entry) { - send_prio = p; - break; + // Look for must_send FIGs in priority order + for (int prio_offset = 0; prio_offset < NUM_PRIORITIES - 1 && remaining > 2; prio_offset++) { + int prio = start_prio + prio_offset; + if (prio >= NUM_PRIORITIES) { + prio = 1 + (prio - NUM_PRIORITIES); // Wrap around, skip 0 + } + + if (!m_priorities[prio].has_must_send()) { + continue; + } + + // Try all must_send FIGs in this priority + size_t carousel_size = m_priorities[prio].carousel.size(); + for (size_t fig_attempt = 0; fig_attempt < carousel_size && remaining > 2; fig_attempt++) { + FIGEntryPriority* entry = m_priorities[prio].find_must_send(); + if (!entry) { + break; // No more must_send in this priority + } + + // Skip if we've already tried this FIG this FIB + if (tried_this_fib.count(entry)) { + m_priorities[prio].move_to_back(entry); + continue; + } + tried_this_fib.insert(entry); + + size_t bytes = try_send_fig(entry, pbuf, remaining); + if (bytes > 0) { +#if PRIORITY_CAROUSEL_DEBUG + fib_log << entry->name() << ":" << bytes << "B "; +#endif + written += bytes; + remaining -= bytes; + pbuf += bytes; + m_priorities[prio].move_to_back(entry); + on_fig_sent(prio); + made_progress = true; + } else { + // FIG couldn't write (doesn't fit), move to back and try next + m_priorities[prio].move_to_back(entry); } } } + } + + // Step 3: Can-send pass - fill remaining space opportunistically + // By now, there should be no must_send FIGs (cleared in step 2) + // Any FIG can send to fill the FIB and improve repetition rates + // + // TWO-PASS APPROACH to avoid over-serving FIGs that are ahead of schedule: + // Pass 1: Only try FIGs that are past 50% of their deadline (actually need bandwidth) + // Pass 2: If still space, try any FIG that can_send (avoid wasting FIB space) + // + // This ensures bandwidth goes to FIGs that need it (like 1/1 at ~1200ms vs 960ms target) + // rather than FIGs already well ahead of schedule (like 0/9 at ~500ms vs 960ms target) + // + // NOTE: We do NOT update the priority counters here. The counter system + // is for bandwidth allocation in must_send. In can_send, we just fill space. + + tried_this_fib.clear(); + + // Pass 1: FIGs that are past 50% of their deadline (need bandwidth more urgently) + for (int prio = 1; prio < NUM_PRIORITIES && remaining > 2; prio++) { + if (m_priorities[prio].carousel.empty()) { + continue; + } - if (!entry) break; // No more must_send anywhere - - size_t bytes = try_send_fig(entry, pbuf, remaining); - if (bytes > 0) { + size_t carousel_size = m_priorities[prio].carousel.size(); + for (size_t fig_attempt = 0; fig_attempt < carousel_size && remaining > 2; fig_attempt++) { + FIGEntryPriority* entry = m_priorities[prio].find_can_send(); + if (!entry) { + break; + } + + // Skip if we've already tried this FIG this FIB + if (tried_this_fib.count(entry)) { + m_priorities[prio].move_to_back(entry); + continue; + } + + // Pass 1: Only try FIGs past 50% of their deadline + if (!entry->past_deadline_percent(50)) { + tried_this_fib.insert(entry); + m_priorities[prio].move_to_back(entry); + continue; + } + + tried_this_fib.insert(entry); + + size_t bytes = try_send_fig(entry, pbuf, remaining); + if (bytes > 0) { #if PRIORITY_CAROUSEL_DEBUG - fib_log << entry->name() << ":" << bytes << "B "; + fib_log << entry->name() << ":" << bytes << "B(urg) "; #endif - written += bytes; - remaining -= bytes; - pbuf += bytes; - m_priorities[send_prio].move_to_back(entry); - on_fig_sent(send_prio); - } else { - // FIG couldn't write (no space or no data), move to back to try others - m_priorities[send_prio].move_to_back(entry); + written += bytes; + remaining -= bytes; + pbuf += bytes; + } + m_priorities[prio].move_to_back(entry); } } - // Step 3: Can-send pass (fill remaining space opportunistically) - attempts = 0; - while (remaining > 2 && attempts < max_attempts) { - attempts++; - - int prio = select_priority(); - if (prio < 0) break; - - FIGEntryPriority* entry = m_priorities[prio].find_can_send(); + // Pass 2: Any remaining FIGs that can send (to fill unused space) + // Reset tried set - we want to try everything again, but only those we skipped + std::set<FIGEntryPriority*> tried_pass1 = tried_this_fib; + + for (int prio = 1; prio < NUM_PRIORITIES && remaining > 2; prio++) { + if (m_priorities[prio].carousel.empty()) { + continue; + } - if (!entry) { - // Try other priorities - for (int p = 1; p < NUM_PRIORITIES; p++) { - if (!m_priorities[p].carousel.empty()) { - entry = m_priorities[p].carousel.front(); - prio = p; - break; + size_t carousel_size = m_priorities[prio].carousel.size(); + for (size_t fig_attempt = 0; fig_attempt < carousel_size && remaining > 2; fig_attempt++) { + FIGEntryPriority* entry = m_priorities[prio].find_can_send(); + if (!entry) { + break; + } + + // Skip if already sent in pass 1 + if (tried_pass1.count(entry) && tried_this_fib.count(entry)) { + // Check if it was actually sent (not just tried and skipped) + // If it was skipped due to not being urgent, we can try it now + if (entry->past_deadline_percent(50)) { + // Was tried and is urgent - was actually attempted, skip + m_priorities[prio].move_to_back(entry); + continue; } } - } - - if (!entry) break; - - size_t bytes = try_send_fig(entry, pbuf, remaining); - if (bytes > 0) { + + tried_this_fib.insert(entry); + + size_t bytes = try_send_fig(entry, pbuf, remaining); + if (bytes > 0) { #if PRIORITY_CAROUSEL_DEBUG - fib_log << entry->name() << ":" << bytes << "B(opt) "; + fib_log << entry->name() << ":" << bytes << "B(fill) "; #endif - written += bytes; - remaining -= bytes; - pbuf += bytes; - m_priorities[prio].move_to_back(entry); - on_fig_sent(prio); - } else { - // FIG wrote nothing, move on + written += bytes; + remaining -= bytes; + pbuf += bytes; + } m_priorities[prio].move_to_back(entry); - break; // No more space or nothing to send } } #if PRIORITY_CAROUSEL_DEBUG fib_log << "= " << written << "/" << max_size; - etiLog.level(debug) << fib_log.str(); + etiLog.level(info) << fib_log.str(); #endif return written; } -size_t FIGCarouselPriority::send_priority_zero(uint8_t* buf, size_t max_size, int framephase) +size_t FIGCarouselPriority::send_priority_zero(uint8_t* buf, size_t max_size, int fib_index, int framephase) { size_t written = 0; - // FIG 0/0 and 0/7 only in framephase 0 (every 96ms in mode I) - if (framephase != 0) { + // EN 300 401 Clause 6.4.1: FIG 0/0 shall only be transmitted in the first + // FIB of the first CIF of each FIC (once per 96ms in Mode I) + // It must be the first FIG in that FIB + + // Only send in FIB 0 at framephase 0 + if (fib_index != 0 || framephase != 0) { return 0; } #if PRIORITY_CAROUSEL_DEBUG - etiLog.level(debug) << "send_priority_zero: framephase=0, sending 0/0 and 0/7"; + etiLog.level(info) << "send_priority_zero: FIB0, framephase=0, sending 0/0 and 0/7"; #endif // Find and send FIG 0/0 @@ -443,17 +592,18 @@ size_t FIGCarouselPriority::send_priority_zero(uint8_t* buf, size_t max_size, in if (entry->fig->figtype() == 0 && entry->fig->figextension() == 0) { FillStatus status = entry->fig->fill(buf + written, max_size - written); #if PRIORITY_CAROUSEL_DEBUG - etiLog.level(debug) << " FIG 0/0: wrote " << status.num_bytes_written + etiLog.level(info) << " FIG 0/0: wrote " << status.num_bytes_written << " bytes, complete=" << status.complete_fig_transmitted << ", deadline was " << entry->deadline_ms << "ms"; #endif if (status.num_bytes_written > 0) { written += status.num_bytes_written; - if (status.complete_fig_transmitted) { - entry->on_cycle_complete(); - } } - else { + // Mark cycle complete if the FIG says so + if (status.complete_fig_transmitted) { + entry->on_cycle_complete(status.num_bytes_written > 0); + } + if (status.num_bytes_written == 0) { throw std::logic_error("Failed to write FIG 0/0"); } break; @@ -464,23 +614,23 @@ size_t FIGCarouselPriority::send_priority_zero(uint8_t* buf, size_t max_size, in for (auto* entry : m_priorities[0].carousel) { if (entry->fig->figtype() == 0 && entry->fig->figextension() == 7) { #if PRIORITY_CAROUSEL_DEBUG - etiLog.level(debug) << " FIG 0/7: deadline=" << entry->deadline_ms + etiLog.level(info) << " FIG 0/7: deadline=" << entry->deadline_ms << "ms, must_send=" << entry->must_send << ", violated=" << entry->deadline_violated; #endif FillStatus status = entry->fig->fill(buf + written, max_size - written); #if PRIORITY_CAROUSEL_DEBUG - etiLog.level(debug) << " FIG 0/7: wrote " << status.num_bytes_written + etiLog.level(info) << " FIG 0/7: wrote " << status.num_bytes_written << " bytes, complete=" << status.complete_fig_transmitted; #endif if (status.num_bytes_written > 0) { written += status.num_bytes_written; } - // If complete (even with 0 bytes - means nothing to send), reset the cycle + // If complete, reset the cycle - pass whether data was sent if (status.complete_fig_transmitted) { - entry->on_cycle_complete(); + entry->on_cycle_complete(status.num_bytes_written > 0); #if PRIORITY_CAROUSEL_DEBUG - etiLog.level(debug) << " FIG 0/7: cycle complete, new deadline=" << entry->deadline_ms; + etiLog.level(info) << " FIG 0/7: cycle complete, new deadline=" << entry->deadline_ms; #endif } break; @@ -523,6 +673,40 @@ int FIGCarouselPriority::select_priority() return best_prio; } +int FIGCarouselPriority::cascade_find_priority(int start, bool must_send_only) +{ + // Cascade from start priority down to 8, then wrap to 1 and continue + // until we reach start again (full circle) + + // First pass: start -> NUM_PRIORITIES-1 + for (int p = start; p < NUM_PRIORITIES; p++) { + if (must_send_only) { + if (m_priorities[p].has_must_send()) { + return p; + } + } else { + if (m_priorities[p].has_can_send()) { + return p; + } + } + } + + // Second pass: 1 -> start-1 (wrap around, skip priority 0) + for (int p = 1; p < start; p++) { + if (must_send_only) { + if (m_priorities[p].has_must_send()) { + return p; + } + } else { + if (m_priorities[p].has_can_send()) { + return p; + } + } + } + + return -1; // Nothing found in any priority +} + void FIGCarouselPriority::on_fig_sent(int priority) { // Decrement all counters @@ -567,16 +751,10 @@ size_t FIGCarouselPriority::try_send_fig(FIGEntryPriority* entry, uint8_t* buf, throw std::logic_error(ss.str()); } -#if PRIORITY_CAROUSEL_DEBUG - if (written > 0) { - etiLog.level(debug) << "FIGCarouselPriority: " << entry->name() - << " wrote " << written << " bytes" - << (status.complete_fig_transmitted ? " (complete)" : " (partial)"); - } -#endif - + // Handle cycle completion + // Pass whether data was actually sent - only record stats if we transmitted something if (status.complete_fig_transmitted) { - entry->on_cycle_complete(); + entry->on_cycle_complete(written > 0); } return written; |
