datactl/timesync.h

datactl/timesync.h

datactl/timesync.h

Namespaces

Name
Syntalos

Classes

Name
classSyntalos::FreqCounterSynchronizer
Synchronizer for a monotonic counter, given a frequency.
classSyntalos::SecondaryClockSynchronizer
Synchronizer for an external steady monotonic clock.

Source code

/*
 * Copyright (C) 2019-2024 Matthias Klumpp <matthias@tenstral.net>
 *
 * Licensed under the GNU Lesser General Public License Version 3
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the license, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <QDataStream>
#include <QLoggingCategory>
#include <QMetaType>
#include <QString>
#include <QUuid>
#include <fstream>
#include <memory>

#include "datactl/eigenaux.h"
#include "datactl/syclock.h"
#include "datactl/tsyncfile.h"

class QFile;

namespace Syntalos
{

Q_DECLARE_LOGGING_CATEGORY(logTimeSync)


static constexpr auto SECONDARY_CLOCK_TOLERANCE = microseconds_t(1000);

static constexpr auto DEFAULT_CLOCKSYNC_CHECK_INTERVAL = milliseconds_t(4000);

enum class TimeSyncStrategy {
    NONE = 0,
    SHIFT_TIMESTAMPS_FWD = 1 << 0, 
    SHIFT_TIMESTAMPS_BWD = 1 << 1, 
    ADJUST_CLOCK = 1 << 2,         
    WRITE_TSYNCFILE =
        1 << 3 
};
Q_DECLARE_FLAGS(TimeSyncStrategies, TimeSyncStrategy)
Q_DECLARE_OPERATORS_FOR_FLAGS(TimeSyncStrategies)

const QString timeSyncStrategyToHString(const TimeSyncStrategy &strategy);
const QString timeSyncStrategiesToHString(const TimeSyncStrategies &strategies);

} // namespace Syntalos

Q_DECLARE_METATYPE(Syntalos::TimeSyncStrategies);

namespace Syntalos
{

using SyncDetailsChangeNotifyFn =
    std::function<void(const QString &id, const TimeSyncStrategies &strategies, const microseconds_t &tolerance)>;

using OffsetChangeNotifyFn = std::function<void(const QString &id, const microseconds_t &currentOffset)>;

class FreqCounterSynchronizer
{
public:
    explicit FreqCounterSynchronizer(
        std::shared_ptr<SyncTimer> masterTimer,
        const QString &modName,
        double frequencyHz,
        const QString &id = nullptr);
    ~FreqCounterSynchronizer();

    void setNotifyCallbacks(
        const SyncDetailsChangeNotifyFn &detailsChangeNotifyFn,
        const OffsetChangeNotifyFn &offsetChangeNotifyFn);

    void setCalibrationBlocksCount(int count);
    void setStrategies(const TimeSyncStrategies &strategies);
    void setTolerance(const std::chrono::microseconds &tolerance);
    void setTimeSyncBasename(const QString &fname, const QUuid &collectionId);
    void setLastValidMasterTimestamp(microseconds_t masterTimestamp);

    microseconds_t lastMasterAssumedAcqTS() const;

    bool isCalibrated() const;
    int indexOffset() const;

    bool start();
    void stop();
    void processTimestamps(
        const microseconds_t &blocksRecvTimestamp,
        int blockIndex,
        int blockCount,
        VectorXul &idxTimestamps);

private:
    Q_DISABLE_COPY(FreqCounterSynchronizer)

    QString m_modName;
    QUuid m_collectionId;
    QString m_id;
    TimeSyncStrategies m_strategies;
    microseconds_t m_lastOffsetEmission;
    std::shared_ptr<SyncTimer> m_syTimer;

    SyncDetailsChangeNotifyFn m_detailsChangeNotifyFn;
    OffsetChangeNotifyFn m_offsetChangeNotifyFn;

    uint m_toleranceUsec;
    bool m_lastOffsetWithinTolerance;

    uint m_calibrationMaxBlockN;
    uint m_calibrationIdx;
    VectorXsl m_tsOffsetsUsec;

    bool m_haveExpectedOffset;
    uint m_expectedOffsetCalCount;
    microseconds_t m_expectedOffset;
    double m_expectedSD;

    uint m_offsetChangeWaitBlocks;
    microseconds_t m_timeCorrectionOffset;
    uint m_lastTimeIndex;

    double m_freq;
    double m_timePerPointUs;
    int m_indexOffset;
    bool m_applyIndexOffset;

    uint m_lastSecondaryIdxUnandjusted;
    microseconds_t m_lastMasterAssumedAcqTS;
    microseconds_t m_lastValidMasterTimestamp;

    std::unique_ptr<TimeSyncFileWriter> m_tswriter;
};

class SecondaryClockSynchronizer
{
public:
    explicit SecondaryClockSynchronizer(
        std::shared_ptr<SyncTimer> masterTimer,
        const QString &modName,
        const QString &id = QString());
    ~SecondaryClockSynchronizer();

    void setNotifyCallbacks(
        const SyncDetailsChangeNotifyFn &detailsChangeNotifyFn,
        const OffsetChangeNotifyFn &offsetChangeNotifyFn);

    microseconds_t clockCorrectionOffset() const;

    void setCalibrationPointsCount(int timepointCount);

    void setExpectedClockFrequencyHz(double frequency);

    void setStrategies(const TimeSyncStrategies &strategies);
    void setTolerance(const microseconds_t &tolerance);
    void setTimeSyncBasename(const QString &fname, const QUuid &collectionId);

    bool isCalibrated() const;
    microseconds_t expectedOffsetToMaster() const;

    bool start();
    void stop();
    void processTimestamp(microseconds_t &masterTimestamp, const microseconds_t &secondaryAcqTimestamp);

private:
    Q_DISABLE_COPY(SecondaryClockSynchronizer)

    void emitSyncDetailsChanged();

    QString m_modName;
    QUuid m_collectionId;
    QString m_id;
    TimeSyncStrategies m_strategies;
    microseconds_t m_lastOffsetEmission;
    std::shared_ptr<SyncTimer> m_syTimer;

    SyncDetailsChangeNotifyFn m_detailsChangeNotifyFn;
    OffsetChangeNotifyFn m_offsetChangeNotifyFn;

    uint m_toleranceUsec;
    bool m_lastOffsetWithinTolerance;

    uint m_calibrationMaxN;
    uint m_calibrationIdx;
    VectorXsl m_clockOffsetsUsec;

    bool m_haveExpectedOffset;
    uint m_expectedOffsetCalCount;
    microseconds_t m_expectedOffset;
    double m_expectedSD;

    microseconds_t m_clockCorrectionOffset;
    microseconds_t m_lastMasterTS;
    microseconds_t m_lastSecondaryAcqTS;
    uint m_clockUpdateWaitPoints;

    std::unique_ptr<TimeSyncFileWriter> m_tswriter;
};

template<typename T>
void safeStopSynchronizer(const T &synchronizerSmartPtr)
{
    static_assert(
        std::is_base_of<SecondaryClockSynchronizer, typename T::element_type>::value
            || std::is_base_of<FreqCounterSynchronizer, typename T::element_type>::value,
        "This function requires a smart pointer to a clock synchronizer.");
    if (synchronizerSmartPtr.get() != nullptr)
        synchronizerSmartPtr->stop();
}

template<typename T>
void safeStopSynchronizer(const T &synchronizer, const microseconds_t &lastValidMasterTimestamp)
{
    static_assert(
        std::is_base_of<FreqCounterSynchronizer, typename T::element_type>::value,
        "This function requires a smart pointer to a FreqCounter clock synchronizer.");
    if (synchronizer) {
        synchronizer->setLastValidMasterTimestamp(lastValidMasterTimestamp);
        safeStopSynchronizer(synchronizer);
    }
}

} // namespace Syntalos

Updated on 2024-09-05 at 17:39:59 +0000