aboutsummaryrefslogtreecommitdiffstats
path: root/src/fig/FIGCarouselPriority.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/fig/FIGCarouselPriority.cpp')
-rw-r--r--src/fig/FIGCarouselPriority.cpp538
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;