262TEST_CASE(
"mcp3208Ctrl Doxygen references are preserved",
"[mcp3208Ctrl]" )
264 mcp3208Ctrl_test app;
266 app.setupFpsProperty();
267 app.setupFpsSourceProperty();
268 app.setupAlphaProperty();
269 app.setupSynchroDelayProperty();
282 XWCTEST_DOXYGEN_REF( app.setCallBack_m_indiP_fpsSource( app.makeFpsSourceUpdate( 1000.0 ) ) );
284 XWCTEST_DOXYGEN_REF( app.newCallBack_m_indiP_synchroDelay( app.makeSynchroDelayUpdate( 1.0 ) ) );
299TEST_CASE(
"mcp3208Ctrl configuration defaults load synchronized settings",
"[mcp3208Ctrl]" )
301 mcp3208Ctrl_test app;
305 mx::app::writeConfigFile(
"/tmp/mcp3208Ctrl_test.conf", {
"none" }, {
"nada" }, {
"0" } );
306 app.config.readConfig(
"/tmp/mcp3208Ctrl_test.conf" );
308 REQUIRE( app.loadConfigImpl( app.config ) == 0 );
309 REQUIRE( app.m_synchroShmimName.empty() );
310 REQUIRE( app.m_synchroPostDelay == 0 );
311 REQUIRE( app.m_synchroDtTransfer_ns == Approx( 3000.0 ) );
312 REQUIRE( app.m_synchroWfsProcess_ns == Approx( 51500.0 ) );
313 REQUIRE( app.m_synchroDtF_ns == Approx( 10000.0 ) );
314 REQUIRE( app.m_synchroWfsRead_ns == Approx( 276100.0 ) );
315 REQUIRE( app.m_delayLockAbsThreshold_ns == Approx( 50000.0 ) );
316 REQUIRE( app.m_delayLockFracThreshold == Approx( 0.1 ) );
317 REQUIRE( app.m_cadenceGuard_ns == Approx( 20000.0 ) );
318 REQUIRE( app.m_alpha == Approx( 0.01f ) );
319 REQUIRE( app.m_synchroDelayTarget == Approx( 0.0f ) );
320 REQUIRE( app.m_synchroDelay == Approx( 0.0f ) );
321 REQUIRE( app.m_wfs_fps == Approx(
static_cast<double>( app.m_fps ) ) );
328TEST_CASE(
"mcp3208Ctrl configuration overrides load synchronized settings",
"[mcp3208Ctrl]" )
330 mcp3208Ctrl_test app;
334 mx::app::writeConfigFile(
"/tmp/mcp3208Ctrl_test_override.conf",
352 "delayLockAbsThreshold_ns",
353 "delayLockFracThreshold",
357 {
"camwfs_sync",
"17",
"4000",
"62000",
"11000",
"290000",
"75000",
"0.2",
"15000",
"0.25",
"3" } );
358 app.config.readConfig(
"/tmp/mcp3208Ctrl_test_override.conf" );
360 REQUIRE( app.loadConfigImpl( app.config ) == 0 );
361 REQUIRE( app.m_synchroShmimName ==
"camwfs_sync" );
362 REQUIRE( app.m_synchroPostDelay == 17 );
363 REQUIRE( app.m_synchroDtTransfer_ns == Approx( 4000.0 ) );
364 REQUIRE( app.m_synchroWfsProcess_ns == Approx( 62000.0 ) );
365 REQUIRE( app.m_synchroDtF_ns == Approx( 11000.0 ) );
366 REQUIRE( app.m_synchroWfsRead_ns == Approx( 290000.0 ) );
367 REQUIRE( app.m_delayLockAbsThreshold_ns == Approx( 75000.0 ) );
368 REQUIRE( app.m_delayLockFracThreshold == Approx( 0.2 ) );
369 REQUIRE( app.m_cadenceGuard_ns == Approx( 15000.0 ) );
370 REQUIRE( app.m_alpha == Approx( 0.25f ) );
371 REQUIRE( app.m_numChannels == 3 );
372 REQUIRE( app.m_synchroDelayTarget == Approx( 17000.0f ) );
373 REQUIRE( app.m_synchroDelay == Approx( 17000.0f ) );
374 REQUIRE( app.m_wfs_fps == Approx(
static_cast<double>( app.m_fps ) ) );
456TEST_CASE(
"mcp3208Ctrl synchroDelay callback updates signed offsets",
"[mcp3208Ctrl]" )
458 mcp3208Ctrl_test app;
460 app.setupSynchroDelayProperty();
461 app.m_wfsPeriodMeasured_ns = 1000000.0;
462 app.m_delayModel_ns = 120000.0;
464 REQUIRE( app.newCallBack_m_indiP_synchroDelay( app.makeSynchroDelayUpdate( 25.0 ) ) == 0 );
465 REQUIRE( app.m_synchroPostDelay == 25 );
466 REQUIRE( app.m_synchroDelayTarget == Approx( 145000.0f ) );
467 REQUIRE( app.m_synchroDelay == Approx( 145000.0f ) );
468 REQUIRE( app.m_delayApplied_ns == Approx( 145000.0 ) );
469 REQUIRE( app.m_delayModel_ns == Approx( 120000.0 ) );
471 REQUIRE( app.newCallBack_m_indiP_synchroDelay( app.makeSynchroDelayUpdate( -5.0 ) ) == 0 );
472 REQUIRE( app.m_synchroPostDelay == -5 );
473 REQUIRE( app.m_synchroDelayTarget == Approx( 115000.0f ) );
474 REQUIRE( app.m_synchroDelay == Approx( 115000.0f ) );
475 REQUIRE( app.m_delayApplied_ns == Approx( 115000.0 ) );
476 REQUIRE( app.m_delayModel_ns == Approx( 120000.0 ) );
483TEST_CASE(
"mcp3208Ctrl timing diagnostics publish synchronized loop metrics",
"[mcp3208Ctrl]" )
485 mcp3208Ctrl_test app;
487 app.setupTimingDiagnosticsProperty();
488 app.m_synchroShmimName =
"camwfs_sync";
489 app.m_avgReadLatency_ns = 125000.0;
490 app.m_synchroDelay = 31000.0f;
491 app.m_synchroDelayTarget = 17000.0f;
492 app.m_delayApplied_ns = 24000.0;
493 app.m_delayModel_ns = 17000.0;
494 app.m_avgSemaphorePeriod_ns = 500000.0;
495 app.m_wfsPeriodMeasured_ns = 500000.0;
496 app.m_producerPeriodInst_ns = 510000.0;
497 app.m_avgProducerPeriod_ns = 500000.0;
498 app.m_channelReadoutTime_ns = 34000.0;
499 app.m_triggerInterval_ns = 600000.0;
500 app.m_atime = timespec{ 12, 3000000L };
501 app.m_triggerTime = timespec{ 12, 3456789L };
502 app.m_localFrameSeq = 44;
503 app.m_syncFramesReceived = 41;
504 app.m_syncFramesWritten = 40;
505 app.m_syncFramesDropped = 2;
506 app.m_syncFrameIdGapCount = 2;
507 app.m_syncProducerFrameId = 123456;
508 app.m_syncProducerFrameDelta = 3;
509 app.m_syncProducerFrameValid =
true;
511 app.updateTimingDiagnosticsIndi();
513 REQUIRE( app.m_indiP_timingDiag[
"avg_read_latency_us"].get<
double>() == Approx( 125.0 ) );
514 REQUIRE( app.m_indiP_timingDiag[
"synchro_delay_us"].get<
double>() == Approx( 31.0 ) );
515 REQUIRE( app.m_indiP_timingDiag[
"synchro_delay_target_us"].get<
double>() == Approx( 17.0 ) );
516 REQUIRE( app.m_indiP_timingDiag[
"delay_applied_us"].get<
double>() == Approx( 24.0 ) );
517 REQUIRE( app.m_indiP_timingDiag[
"delay_model_us"].get<
double>() == Approx( 17.0 ) );
518 REQUIRE( app.m_indiP_timingDiag[
"delay_phase_error_us"].get<
double>() == Approx( 7.0 ) );
519 REQUIRE( app.m_indiP_timingDiag[
"delay_lock"].get<
double>() == Approx( 1.0 ) );
520 REQUIRE( app.m_indiP_timingDiag[
"read_latency_error_us"].get<
double>() == Approx( 108.0 ) );
521 REQUIRE( app.m_indiP_timingDiag[
"avg_semaphore_period_us"].get<
double>() == Approx( 500.0 ) );
522 REQUIRE( app.m_indiP_timingDiag[
"channel_readout_us"].get<
double>() == Approx( 34.0 ) );
523 REQUIRE( app.m_indiP_timingDiag[
"trigger_interval_us"].get<
double>() == Approx( 600.0 ) );
524 REQUIRE( app.m_indiP_timingDiag[
"trigger_time_us"].get<
double>() ==
527 REQUIRE( app.m_indiP_timingDiag[
"local_frame_seq"].get<
double>() == Approx( 44.0 ) );
528 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_received"].get<
double>() == Approx( 41.0 ) );
529 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_written"].get<
double>() == Approx( 40.0 ) );
530 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_dropped"].get<
double>() == Approx( 2.0 ) );
531 REQUIRE( app.m_indiP_timingDiag[
"sync_frame_id_gap_count"].get<
double>() == Approx( 2.0 ) );
532 REQUIRE( app.m_indiP_timingDiag[
"sync_producer_frame_id"].get<
double>() == Approx( 123456.0 ) );
533 REQUIRE( app.m_indiP_timingDiag[
"sync_producer_frame_delta"].get<
double>() == Approx( 3.0 ) );
534 REQUIRE( app.m_indiP_timingDiag[
"mode_code"].get<
double>() == Approx( 1.0 ) );
541TEST_CASE(
"mcp3208Ctrl timing diagnostics track mode transitions",
"[mcp3208Ctrl]" )
543 mcp3208Ctrl_test app;
545 app.setupTimingDiagnosticsProperty();
546 app.m_synchroShmimName =
"camwfs_sync";
547 app.m_atime = timespec{ 1, 2 };
548 app.m_triggerInterval_ns = 123456.0;
549 app.updateTimingDiagnosticsIndi();
551 REQUIRE( app.m_indiP_timingDiag[
"mode_code"].get<
double>() == Approx( 1.0 ) );
552 REQUIRE( app.m_indiP_timingDiag[
"trigger_interval_us"].get<
double>() == Approx( 123.456 ) );
553 REQUIRE( app.m_indiP_timingDiag[
"trigger_time_us"].get<
double>() == Approx( 0.0 ) );
554 REQUIRE( app.m_indiP_timingDiag[
"delay_phase_error_us"].get<
double>() == Approx( 0.0 ) );
555 REQUIRE( app.m_indiP_timingDiag[
"delay_lock"].get<
double>() == Approx( 0.0 ) );
556 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_received"].get<
double>() == Approx( 0.0 ) );
557 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_written"].get<
double>() == Approx( 0.0 ) );
558 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_dropped"].get<
double>() == Approx( 0.0 ) );
559 REQUIRE( app.m_indiP_timingDiag[
"sync_frame_id_gap_count"].get<
double>() == Approx( 0.0 ) );
560 REQUIRE( app.m_indiP_timingDiag[
"sync_producer_frame_id"].get<
double>() == Approx( 0.0 ) );
561 REQUIRE( app.m_indiP_timingDiag[
"sync_producer_frame_delta"].get<
double>() == Approx( 0.0 ) );
563 app.m_synchroShmimName.clear();
564 app.m_triggerInterval_ns = 456789.0;
565 app.updateTimingDiagnosticsIndi();
567 REQUIRE( app.m_indiP_timingDiag[
"mode_code"].get<
double>() == Approx( 0.0 ) );
568 REQUIRE( app.m_indiP_timingDiag[
"trigger_interval_us"].get<
double>() == Approx( 456.789 ) );
569 REQUIRE( app.m_indiP_timingDiag[
"trigger_time_us"].get<
double>() == Approx( 0.0 ) );
570 REQUIRE( app.m_indiP_timingDiag[
"delay_phase_error_us"].get<
double>() == Approx( 0.0 ) );
571 REQUIRE( app.m_indiP_timingDiag[
"delay_lock"].get<
double>() == Approx( 0.0 ) );
572 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_received"].get<
double>() == Approx( 0.0 ) );
573 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_written"].get<
double>() == Approx( 0.0 ) );
574 REQUIRE( app.m_indiP_timingDiag[
"sync_frames_dropped"].get<
double>() == Approx( 0.0 ) );
575 REQUIRE( app.m_indiP_timingDiag[
"sync_frame_id_gap_count"].get<
double>() == Approx( 0.0 ) );
576 REQUIRE( app.m_indiP_timingDiag[
"sync_producer_frame_id"].get<
double>() == Approx( 0.0 ) );
577 REQUIRE( app.m_indiP_timingDiag[
"sync_producer_frame_delta"].get<
double>() == Approx( 0.0 ) );
626TEST_CASE(
"mcp3208Ctrl updateTriggerTiming uses measured semaphore period for delay model",
"[mcp3208Ctrl]" )
628 mcp3208Ctrl_test app;
631 app.m_firstSemaphore =
false;
632 app.m_lastAtime = timespec{ 10, 100000000L };
633 app.m_avgSemaphorePeriod_ns = 500000.0;
634 app.m_wfs_fps = 1000.0;
635 app.m_synchroPostDelay = 0;
637 const timespec secondArrival{ 10, 101000000L };
639 app.updateTriggerTiming( secondArrival );
641 const double alpha =
static_cast<double>( app.m_alpha );
642 const double expectedAvg_ns = alpha * 1000000.0 + ( 1.0 - alpha ) * 500000.0;
643 const double expectedMeasuredDeltaT_ns = expectedAvg_ns;
644 const double expectedBlendedDeltaT_ns = ( 1.0 - alpha ) * ( 1e9 / 1000.0 ) + alpha * expectedAvg_ns;
645 const double rawDelayMeasured_ns =
646 0.5 * expectedMeasuredDeltaT_ns - ( 3000.0 + 51500.0 + 10000.0 + 276100.0 );
647 const double rawDelayBlended_ns = 0.5 * expectedBlendedDeltaT_ns - ( 3000.0 + 51500.0 + 10000.0 + 276100.0 );
648 const double expectedDelayMeasured_ns = wrapDelay( rawDelayMeasured_ns, expectedMeasuredDeltaT_ns );
649 const double expectedDelayBlended_ns = wrapDelay( rawDelayBlended_ns, expectedBlendedDeltaT_ns );
653 REQUIRE( app.m_avgSemaphorePeriod_ns == Approx( expectedAvg_ns ) );
654 REQUIRE( app.m_wfsPeriodMeasured_ns == Approx( expectedMeasuredDeltaT_ns ) );
655 REQUIRE( app.m_delayModel_ns == Approx( expectedDelayMeasured_ns ) );
656 REQUIRE( app.m_synchroDelayTarget == Approx(
static_cast<float>( expectedDelayMeasured_ns ) ) );
657 REQUIRE( app.m_triggerInterval_ns == Approx( 0.0 ) );
658 REQUIRE( measuredDelay_ns == Approx( expectedDelayMeasured_ns ) );
659 REQUIRE( expectedDelayMeasured_ns != Approx( expectedDelayBlended_ns ) );
660 REQUIRE( measuredDelay_ns >= 0.0 );
661 REQUIRE( measuredDelay_ns < expectedMeasuredDeltaT_ns );
668TEST_CASE(
"mcp3208Ctrl updateTriggerTiming applies positive synchroDelay offset",
"[mcp3208Ctrl]" )
670 mcp3208Ctrl_test app;
673 app.m_firstSemaphore =
false;
674 app.m_lastAtime = timespec{ 0, 0 };
675 app.m_avgSemaphorePeriod_ns = 250000.0;
676 app.m_synchroPostDelay = 100;
678 const timespec secondArrival{ 0, 1000000L };
679 app.updateTriggerTiming( secondArrival );
681 const double expectedPeriod_ns = 1000000.0;
682 const double rawModelDelay_ns = 0.5 * expectedPeriod_ns - ( 3000.0 + 51500.0 + 10000.0 + 276100.0 );
683 const double expectedModel_ns = wrapDelay( rawModelDelay_ns, expectedPeriod_ns );
684 const double expectedTarget_ns = wrapDelay( expectedModel_ns + 100000.0, expectedPeriod_ns );
688 REQUIRE( app.m_wfsPeriodMeasured_ns == Approx( expectedPeriod_ns ) );
689 REQUIRE( app.m_delayModel_ns == Approx( expectedModel_ns ) );
690 REQUIRE( app.m_synchroDelayTarget == Approx(
static_cast<float>( expectedTarget_ns ) ) );
691 REQUIRE( measuredDelay_ns == Approx( expectedTarget_ns ) );
698TEST_CASE(
"mcp3208Ctrl updateTriggerTiming applies negative synchroDelay offset with wrap",
"[mcp3208Ctrl]" )
700 mcp3208Ctrl_test app;
703 app.m_firstSemaphore =
false;
704 app.m_lastAtime = timespec{ 0, 0 };
705 app.m_avgSemaphorePeriod_ns = 250000.0;
706 app.m_synchroPostDelay = -300;
708 const timespec secondArrival{ 0, 1000000L };
709 app.updateTriggerTiming( secondArrival );
711 const double expectedPeriod_ns = 1000000.0;
712 const double rawModelDelay_ns = 0.5 * expectedPeriod_ns - ( 3000.0 + 51500.0 + 10000.0 + 276100.0 );
713 const double expectedModel_ns = wrapDelay( rawModelDelay_ns, expectedPeriod_ns );
714 const double expectedTarget_ns = wrapDelay( expectedModel_ns - 300000.0, expectedPeriod_ns );
718 REQUIRE( app.m_wfsPeriodMeasured_ns == Approx( expectedPeriod_ns ) );
719 REQUIRE( app.m_delayModel_ns == Approx( expectedModel_ns ) );
720 REQUIRE( app.m_synchroDelayTarget == Approx(
static_cast<float>( expectedTarget_ns ) ) );
721 REQUIRE( measuredDelay_ns == Approx( expectedTarget_ns ) );
728TEST_CASE(
"mcp3208Ctrl updateTriggerTiming falls back to EMA period when fps is invalid",
"[mcp3208Ctrl]" )
730 mcp3208Ctrl_test app;
733 app.m_firstSemaphore =
false;
734 app.m_lastAtime = timespec{ 0, 0 };
735 app.m_avgSemaphorePeriod_ns = 1000000.0;
738 const timespec nextArrival{ 0, 2000000L };
739 app.updateTriggerTiming( nextArrival );
741 const double alpha =
static_cast<double>( app.m_alpha );
742 const double expectedAvg_ns = alpha * 2000000.0 + ( 1.0 - alpha ) * 1000000.0;
743 const double rawDelay_ns = 0.5 * expectedAvg_ns - ( 3000.0 + 51500.0 + 10000.0 + 276100.0 );
744 const double expectedDelay_ns = wrapDelay( rawDelay_ns, expectedAvg_ns );
749 REQUIRE( app.m_avgSemaphorePeriod_ns == Approx( expectedAvg_ns ) );
750 REQUIRE( measuredTrigger_ns == Approx( expectedTrigger_ns ) );
751 REQUIRE( app.m_synchroDelayTarget == Approx(
static_cast<float>( expectedDelay_ns ) ) );
752 REQUIRE( measuredDelay_ns >= 0.0 );
753 REQUIRE( measuredDelay_ns < expectedAvg_ns );
760TEST_CASE(
"mcp3208Ctrl updateTriggerTiming wraps delay with modulo period",
"[mcp3208Ctrl]" )
762 mcp3208Ctrl_test app;
764 app.m_firstSemaphore =
false;
765 app.m_lastAtime = timespec{ 5, 0 };
766 app.m_avgSemaphorePeriod_ns = 100000.0;
767 app.m_wfs_fps = 20000.0;
769 const timespec nextArrival{ 5, 100000L };
770 app.updateTriggerTiming( nextArrival );
772 const double expectedAvg_ns = 100000.0;
773 const double expectedDeltaT_ns = expectedAvg_ns;
774 const double rawDelay_ns = 0.5 * expectedDeltaT_ns - ( 3000.0 + 51500.0 + 10000.0 + 276100.0 );
775 const double expectedDelay_ns = wrapDelay( rawDelay_ns, expectedDeltaT_ns );
778 REQUIRE( app.m_avgSemaphorePeriod_ns == Approx( expectedAvg_ns ) );
779 REQUIRE( rawDelay_ns < 0.0 );
780 REQUIRE( app.m_synchroDelayTarget == Approx(
static_cast<float>( expectedDelay_ns ) ) );
781 REQUIRE( measuredDelay_ns == Approx( expectedDelay_ns ) );
782 REQUIRE( measuredDelay_ns >= 0.0 );
783 REQUIRE( measuredDelay_ns < expectedDeltaT_ns );
818TEST_CASE(
"mcp3208Ctrl updateTriggerTiming uses configurable timing constants",
"[mcp3208Ctrl]" )
820 mcp3208Ctrl_test app;
822 app.m_firstSemaphore =
false;
823 app.m_lastAtime = timespec{ 9, 0 };
824 app.m_avgSemaphorePeriod_ns = 1200000.0;
825 app.m_wfs_fps = 1000.0;
826 app.m_synchroDtTransfer_ns = 4000.0;
827 app.m_synchroWfsProcess_ns = 62000.0;
828 app.m_synchroDtF_ns = 11000.0;
829 app.m_synchroWfsRead_ns = 290000.0;
831 const timespec nextArrival{ 9, 1200000L };
832 app.updateTriggerTiming( nextArrival );
834 const double expectedAvg_ns = 1200000.0;
835 const double rawDelayCustom_ns = 0.5 * expectedAvg_ns - ( 4000.0 + 62000.0 + 11000.0 + 290000.0 );
836 const double rawDelayDefault_ns = 0.5 * expectedAvg_ns - ( 3000.0 + 51500.0 + 10000.0 + 276100.0 );
837 const double expectedDelayCustom_ns = wrapDelay( rawDelayCustom_ns, expectedAvg_ns );
838 const double measuredDelay_ns =
841 REQUIRE( app.m_avgSemaphorePeriod_ns == Approx( expectedAvg_ns ) );
842 REQUIRE( app.m_delayModel_ns == Approx( expectedDelayCustom_ns ) );
843 REQUIRE( app.m_synchroDelayTarget == Approx(
static_cast<float>( expectedDelayCustom_ns ) ) );
844 REQUIRE( measuredDelay_ns == Approx( expectedDelayCustom_ns ) );
845 REQUIRE( expectedDelayCustom_ns != Approx( wrapDelay( rawDelayDefault_ns, expectedAvg_ns ) ) );
852TEST_CASE(
"mcp3208Ctrl updateTriggerTiming guards non-positive period",
"[mcp3208Ctrl]" )
854 mcp3208Ctrl_test app;
856 app.m_triggerTime = timespec{ 7, 12345L };
857 app.m_triggerInterval_ns = 42.0;
858 app.m_synchroDelayTarget = 12345.0f;
859 app.m_delayModel_ns = 12345.0;
860 app.m_wfsPeriodMeasured_ns = 67890.0;
861 app.m_firstSemaphore =
true;
864 const timespec nextArrival{ 7, 54321L };
865 app.updateTriggerTiming( nextArrival );
867 REQUIRE( app.m_firstSemaphore ==
false );
868 REQUIRE( app.m_avgSemaphorePeriod_ns == Approx( 0.0 ) );
869 REQUIRE( app.m_lastAtime.tv_sec == nextArrival.tv_sec );
870 REQUIRE( app.m_lastAtime.tv_nsec == nextArrival.tv_nsec );
871 REQUIRE( app.m_triggerTime.tv_sec == 7 );
872 REQUIRE( app.m_triggerTime.tv_nsec == 12345L );
873 REQUIRE( app.m_triggerInterval_ns == Approx( 0.0 ) );
874 REQUIRE( app.m_synchroDelayTarget == Approx( 12345.0f ) );
875 REQUIRE( app.m_delayModel_ns == Approx( 12345.0 ) );
876 REQUIRE( app.m_wfsPeriodMeasured_ns == Approx( 0.0 ) );
983TEST_CASE(
"mcp3208Ctrl synchronized mode reads on semaphore wake",
"[mcp3208Ctrl]" )
985 mcp3208Ctrl_test app;
989 stubState().m_channelValues = { 101, 202, 303 };
991 REQUIRE( sem_init( &semaphore, 0, 0 ) == 0 );
992 REQUIRE( sem_post( &semaphore ) == 0 );
994 app.m_synchroShmimName =
"camwfs_sync";
995 app.m_numChannels = 3;
996 app.m_values.assign( 3, 0 );
997 app.m_synchroSemaphore = &semaphore;
998 app.m_synchroDelay = 0;
999 app.m_synchroDelayTarget = 0;
1001 app.m_wfs_fps = 2000.0;
1003 REQUIRE( app.acquireAndCheckValid() == 0 );
1004 REQUIRE( app.m_values == std::vector<uint16_t>( { 101, 202, 303 } ) );
1005 REQUIRE( stubState().m_readOrder == std::vector<int>( { 0, 1, 2 } ) );
1006 REQUIRE( app.m_firstSemaphore ==
false );
1007 REQUIRE( app.m_lastAtime.tv_sec > 0 );
1008 REQUIRE( app.m_triggerTime.tv_sec > 0 );
1009 REQUIRE( app.m_currImageTimestamp.tv_sec > 0 );
1010 REQUIRE( app.m_firstReadLatency ==
false );
1011 REQUIRE( app.m_avgReadLatency_ns ==
1014 REQUIRE( sem_destroy( &semaphore ) == 0 );
1021TEST_CASE(
"mcp3208Ctrl synchronized mode tracks producer cadence from metadata",
"[mcp3208Ctrl]" )
1023 mcp3208Ctrl_test app;
1025 IMAGE_METADATA metadata{};
1028 stubState().m_channelValues = { 44 };
1030 REQUIRE( sem_init( &semaphore, 0, 0 ) == 0 );
1032 app.m_synchroShmimName =
"camwfs_sync";
1033 app.m_numChannels = 1;
1034 app.m_values.assign( 1, 0 );
1035 app.m_synchroSemaphore = &semaphore;
1036 app.m_synchroStream.md = &metadata;
1037 app.m_synchroDelay = 0.0f;
1038 app.m_synchroDelayTarget = 0.0f;
1041 app.m_wfs_fps = 2000.0;
1043 metadata.cnt0 = 100;
1044 metadata.atime = timespec{ 10, 0 };
1045 REQUIRE( sem_post( &semaphore ) == 0 );
1046 REQUIRE( app.acquireAndCheckValid() == 0 );
1048 REQUIRE( app.m_firstProducerSample ==
false );
1049 REQUIRE( app.m_producerPeriodInst_ns == Approx( 0.0 ) );
1050 REQUIRE( app.m_avgProducerPeriod_ns == Approx( 0.0 ) );
1051 REQUIRE( app.m_syncFramesReceived == 1 );
1052 REQUIRE( app.m_syncFramesDropped == 0 );
1053 REQUIRE( app.m_syncFrameIdGapCount == 0 );
1054 REQUIRE( app.m_syncProducerFrameId == 100 );
1055 REQUIRE( app.m_syncProducerFrameDelta == 0 );
1056 REQUIRE( app.m_syncProducerFrameValid ==
true );
1058 metadata.cnt0 = 102;
1059 metadata.atime = timespec{ 10, 1000000L };
1060 REQUIRE( sem_post( &semaphore ) == 0 );
1061 REQUIRE( app.acquireAndCheckValid() == 0 );
1063 REQUIRE( app.m_producerPeriodInst_ns == Approx( 500000.0 ) );
1064 REQUIRE( app.m_avgProducerPeriod_ns == Approx( 500000.0 ) );
1065 REQUIRE( app.m_lastProducerCnt0 == 102 );
1066 REQUIRE( app.m_syncFramesReceived == 2 );
1067 REQUIRE( app.m_syncFramesDropped == 1 );
1068 REQUIRE( app.m_syncFrameIdGapCount == 1 );
1069 REQUIRE( app.m_syncProducerFrameId == 102 );
1070 REQUIRE( app.m_syncProducerFrameDelta == 2 );
1072 metadata.cnt0 = 104;
1073 metadata.atime = timespec{ 10, 2100000L };
1074 REQUIRE( sem_post( &semaphore ) == 0 );
1075 REQUIRE( app.acquireAndCheckValid() == 0 );
1077 const double expectedPeriod2_ns = 550000.0;
1078 const double expectedAvg2_ns =
1079 static_cast<double>( app.m_alpha ) * expectedPeriod2_ns + ( 1.0 -
static_cast<double>( app.m_alpha ) ) * 500000.0;
1080 REQUIRE( app.m_producerPeriodInst_ns == Approx( expectedPeriod2_ns ) );
1081 REQUIRE( app.m_avgProducerPeriod_ns == Approx( expectedAvg2_ns ) );
1082 REQUIRE( app.m_syncFramesReceived == 3 );
1083 REQUIRE( app.m_syncFramesDropped == 2 );
1084 REQUIRE( app.m_syncFrameIdGapCount == 2 );
1085 REQUIRE( app.m_syncProducerFrameId == 104 );
1086 REQUIRE( app.m_syncProducerFrameDelta == 2 );
1088 const double periodBeforeNoAdvance_ns = app.m_producerPeriodInst_ns;
1089 const double avgBeforeNoAdvance_ns = app.m_avgProducerPeriod_ns;
1091 metadata.cnt0 = 104;
1092 metadata.atime = timespec{ 10, 2200000L };
1093 REQUIRE( sem_post( &semaphore ) == 0 );
1094 REQUIRE( app.acquireAndCheckValid() == 0 );
1096 REQUIRE( app.m_producerPeriodInst_ns == Approx( periodBeforeNoAdvance_ns ) );
1097 REQUIRE( app.m_avgProducerPeriod_ns == Approx( avgBeforeNoAdvance_ns ) );
1098 REQUIRE( app.m_syncFramesReceived == 4 );
1099 REQUIRE( app.m_syncFramesDropped == 2 );
1100 REQUIRE( app.m_syncFrameIdGapCount == 2 );
1101 REQUIRE( app.m_syncProducerFrameId == 104 );
1102 REQUIRE( app.m_syncProducerFrameDelta == 0 );
1104 REQUIRE( sem_destroy( &semaphore ) == 0 );
1111TEST_CASE(
"mcp3208Ctrl synchronized read latency EMA initializes and smooths",
"[mcp3208Ctrl]" )
1113 mcp3208Ctrl_test app;
1117 stubState().m_channelValues = { 77 };
1119 REQUIRE( sem_init( &semaphore, 0, 0 ) == 0 );
1121 app.m_synchroShmimName =
"camwfs_sync";
1122 app.m_numChannels = 1;
1123 app.m_values.assign( 1, 0 );
1124 app.m_synchroSemaphore = &semaphore;
1125 app.m_synchroDelayTarget = 0.0f;
1126 app.m_synchroDelay = 0.0f;
1129 app.m_firstReadLatency =
true;
1130 app.m_avgReadLatency_ns = 0.0;
1132 REQUIRE( sem_post( &semaphore ) == 0 );
1133 REQUIRE( app.acquireAndCheckValid() == 0 );
1135 const double readLatency0_ns =
1138 REQUIRE( app.m_firstReadLatency ==
false );
1139 REQUIRE( app.m_avgReadLatency_ns == Approx( readLatency0_ns ) );
1141 REQUIRE( sem_post( &semaphore ) == 0 );
1142 REQUIRE( app.acquireAndCheckValid() == 0 );
1144 const double readLatency1_ns =
1146 const double alpha =
static_cast<double>( app.m_alpha );
1147 const double expectedAvgLatency_ns = alpha * readLatency1_ns + ( 1.0 - alpha ) * readLatency0_ns;
1149 REQUIRE( app.m_avgReadLatency_ns == Approx( expectedAvgLatency_ns ) );
1151 REQUIRE( sem_destroy( &semaphore ) == 0 );
1158TEST_CASE(
"mcp3208Ctrl synchronized non-delay service EMA uses global alpha",
"[mcp3208Ctrl]" )
1160 mcp3208Ctrl_test app;
1164 stubState().m_channelValues = { 55 };
1166 REQUIRE( sem_init( &semaphore, 0, 0 ) == 0 );
1168 app.m_synchroShmimName =
"camwfs_sync";
1169 app.m_numChannels = 1;
1170 app.m_values.assign( 1, 0 );
1171 app.m_synchroSemaphore = &semaphore;
1172 app.m_synchroDelayTarget = 0.0f;
1173 app.m_synchroDelay = 0.0f;
1176 app.m_firstNonDelayService =
true;
1177 app.m_avgNonDelayService_ns = 0.0;
1179 REQUIRE( sem_post( &semaphore ) == 0 );
1180 REQUIRE( app.acquireAndCheckValid() == 0 );
1182 const double nonDelay0_ns = app.m_nonDelayService_ns;
1183 REQUIRE( app.m_firstNonDelayService ==
false );
1184 REQUIRE( app.m_avgNonDelayService_ns == Approx( nonDelay0_ns ) );
1186 REQUIRE( sem_post( &semaphore ) == 0 );
1187 REQUIRE( app.acquireAndCheckValid() == 0 );
1189 const double nonDelay1_ns = app.m_nonDelayService_ns;
1190 const double alpha =
static_cast<double>( app.m_alpha );
1191 const double expectedAvgNonDelay_ns = alpha * nonDelay1_ns + ( 1.0 - alpha ) * nonDelay0_ns;
1193 REQUIRE( app.m_avgNonDelayService_ns == Approx( expectedAvgNonDelay_ns ) );
1195 REQUIRE( sem_destroy( &semaphore ) == 0 );
1202TEST_CASE(
"mcp3208Ctrl synchronized delay controller uses read latency EMA",
"[mcp3208Ctrl]" )
1204 mcp3208Ctrl_test app;
1208 stubState().m_channelValues = { 99 };
1210 REQUIRE( sem_init( &semaphore, 0, 0 ) == 0 );
1211 REQUIRE( sem_post( &semaphore ) == 0 );
1213 app.m_synchroShmimName =
"camwfs_sync";
1214 app.m_numChannels = 1;
1215 app.m_values.assign( 1, 0 );
1216 app.m_synchroSemaphore = &semaphore;
1217 app.m_synchroDelayTarget = 0.0f;
1218 app.m_synchroDelay = 2000000.0f;
1221 app.m_firstReadLatency =
false;
1222 app.m_avgReadLatency_ns = 800000.0;
1224 REQUIRE( app.acquireAndCheckValid() == 0 );
1226 const double readLatency_ns =
1228 const double alpha =
static_cast<double>( app.m_alpha );
1229 const double expectedAvgLatency_ns = alpha * readLatency_ns + ( 1.0 - alpha ) * 800000.0;
1230 const double expectedDelay_ns =
1231 ( 2000000.0 - expectedAvgLatency_ns ) > 0.0 ? ( 2000000.0 - expectedAvgLatency_ns ) : 0.0;
1233 REQUIRE( app.m_avgReadLatency_ns == Approx( expectedAvgLatency_ns ) );
1234 REQUIRE( app.m_synchroDelay == Approx( expectedDelay_ns ) );
1235 REQUIRE( app.m_values[0] == 99 );
1237 REQUIRE( sem_destroy( &semaphore ) == 0 );
1342TEST_CASE(
"mcp3208Ctrl reconfig clears cached synchronization state",
"[mcp3208Ctrl]" )
1344 mcp3208Ctrl_test app;
1346 app.m_synchroSemaphore =
reinterpret_cast<sem_t *
>( 0x1 );
1347 app.m_synchroSemaphoreNumber = 7;
1348 app.m_synchroStreamInode = 1234;
1349 app.m_synchroStreamOpen =
false;
1350 app.m_atime = timespec{ 1, 1 };
1351 app.m_lastAtime = timespec{ 2, 2 };
1352 app.m_avgSemaphorePeriod_ns = 42.0;
1353 app.m_wfsPeriodMeasured_ns = 21.0;
1354 app.m_lastProducerAtime = timespec{ 3, 4 };
1355 app.m_lastProducerCnt0 = 123;
1356 app.m_producerPeriodInst_ns = 500000.0;
1357 app.m_avgProducerPeriod_ns = 510000.0;
1358 app.m_firstProducerSample =
false;
1359 app.m_localFrameSeq = 22;
1360 app.m_syncFramesReceived = 21;
1361 app.m_syncFramesWritten = 20;
1362 app.m_syncFramesDropped = 4;
1363 app.m_syncFrameIdGapCount = 3;
1364 app.m_syncProducerFrameId = 1234567;
1365 app.m_syncProducerFrameDelta = 5;
1366 app.m_lastSyncProducerFrameId = 1234562;
1367 app.m_syncProducerFrameValid =
true;
1368 app.m_firstSemaphore =
false;
1369 app.m_avgReadLatency_ns = 84.0;
1370 app.m_firstReadLatency =
false;
1371 app.m_delayModel_ns = 900.0;
1372 app.m_delayApplied_ns = 875.0;
1373 app.m_delayBudget_ns = 450000.0;
1374 app.m_nonDelayService_ns = 170000.0;
1375 app.m_avgNonDelayService_ns = 160000.0;
1376 app.m_firstNonDelayService =
false;
1377 app.m_delayPhaseError_ns = -25.0;
1378 app.m_delayLock = 1.0;
1379 app.m_delayCapped = 1.0;
1380 app.m_triggerTime = timespec{ 3, 3 };
1381 app.m_triggerInterval_ns = 21.0;
1382 app.m_lastTriggerTime = timespec{ 4, 4 };
1383 app.m_firstTriggerTime =
false;
1384 app.m_firstTimerTrigger =
false;
1386 REQUIRE( app.reconfig() == 0 );
1387 REQUIRE( app.m_synchroSemaphore ==
nullptr );
1388 REQUIRE( app.m_synchroSemaphoreNumber == 5 );
1389 REQUIRE( app.m_synchroStreamInode == 0 );
1390 REQUIRE( app.m_synchroStreamOpen ==
false );
1391 REQUIRE( app.m_atime.tv_sec == 0 );
1392 REQUIRE( app.m_atime.tv_nsec == 0 );
1393 REQUIRE( app.m_lastAtime.tv_sec == 0 );
1394 REQUIRE( app.m_lastAtime.tv_nsec == 0 );
1395 REQUIRE( app.m_avgSemaphorePeriod_ns == Approx( 0.0 ) );
1396 REQUIRE( app.m_wfsPeriodMeasured_ns == Approx( 0.0 ) );
1397 REQUIRE( app.m_lastProducerAtime.tv_sec == 0 );
1398 REQUIRE( app.m_lastProducerAtime.tv_nsec == 0 );
1399 REQUIRE( app.m_lastProducerCnt0 == 0 );
1400 REQUIRE( app.m_producerPeriodInst_ns == Approx( 0.0 ) );
1401 REQUIRE( app.m_avgProducerPeriod_ns == Approx( 0.0 ) );
1402 REQUIRE( app.m_firstProducerSample ==
true );
1403 REQUIRE( app.m_localFrameSeq == 0 );
1404 REQUIRE( app.m_syncFramesReceived == 0 );
1405 REQUIRE( app.m_syncFramesWritten == 0 );
1406 REQUIRE( app.m_syncFramesDropped == 0 );
1407 REQUIRE( app.m_syncFrameIdGapCount == 0 );
1408 REQUIRE( app.m_syncProducerFrameId == 0 );
1409 REQUIRE( app.m_syncProducerFrameDelta == 0 );
1410 REQUIRE( app.m_lastSyncProducerFrameId == 0 );
1411 REQUIRE( app.m_syncProducerFrameValid ==
false );
1412 REQUIRE( app.m_firstSemaphore ==
true );
1413 REQUIRE( app.m_avgReadLatency_ns == Approx( 0.0 ) );
1414 REQUIRE( app.m_firstReadLatency ==
true );
1415 REQUIRE( app.m_delayModel_ns == Approx( 0.0 ) );
1416 REQUIRE( app.m_delayApplied_ns == Approx( 0.0 ) );
1417 REQUIRE( app.m_delayBudget_ns == Approx( 0.0 ) );
1418 REQUIRE( app.m_nonDelayService_ns == Approx( 0.0 ) );
1419 REQUIRE( app.m_avgNonDelayService_ns == Approx( 0.0 ) );
1420 REQUIRE( app.m_firstNonDelayService ==
true );
1421 REQUIRE( app.m_delayPhaseError_ns == Approx( 0.0 ) );
1422 REQUIRE( app.m_delayLock == Approx( 0.0 ) );
1423 REQUIRE( app.m_delayCapped == Approx( 0.0 ) );
1424 REQUIRE( app.m_triggerTime.tv_sec == 0 );
1425 REQUIRE( app.m_triggerTime.tv_nsec == 0 );
1426 REQUIRE( app.m_triggerInterval_ns == Approx( 0.0 ) );
1427 REQUIRE( app.m_lastTriggerTime.tv_sec == 0 );
1428 REQUIRE( app.m_lastTriggerTime.tv_nsec == 0 );
1429 REQUIRE( app.m_firstTriggerTime ==
true );
1430 REQUIRE( app.m_firstTimerTrigger ==
true );