Search code examples
qttimeoutqt5qtimer

How to make a "deadman switch" in Qt?


I want to monitor a periodic action so that I can do something when it stops. For example, switch to a local timer to keep that action going, and switch back to the primary source when it starts back up again.

The best theoretical example that I can come up with is this:

#define FRAME_INTERVAL_MSEC     33  // ~30Hz
bool periodFromUSB;
QTimer* myTimer;

void Processing::localTimeout()
{
    //connected to myTimer->timeout();
    if (periodFromUSB)
    {
        periodFromUSB = false;
        myTimer->stop();
        myTimer->setSingleShot(false);
        myTimer->setInterval(FRAME_INTERVAL_MSEC);
        myTimer->start();
    }

    processDMX();
}

void Processing::newFrame()
{
    //called periodically from my USB driver
    myTimer->stop();
    if(!periodFromUSB)
    {
        periodFromUSB = true;
        myTimer->setSingleShot(true);
        myTimer->setInterval(FRAME_INTERVAL_MSEC * 2);
    }
    myTimer->start();

    processDMX();
}

But no matter how I arrange it, I keep getting both functions called when my USB driver is running. (a breakpoint anywhere in the code above is hit immediately)

Is there a better way to do a deadman?


Solution

  • If you call start(), the timer will be reset to 0 and restart the current interval. That is, as long as your newFrame() slot gets called before the timeout is reached, localTimeout() will never be called.

    Your code would then look like this:

    #define FRAME_INTERVAL_MSEC     33  // ~30Hz
    bool periodFromUSB;
    QTimer* myTimer;
    
    void Processing::localTimeout()
    {
        //connected to myTimer->timeout();
        if (periodFromUSB)
        {
            periodFromUSB = false;
        }
    
        processDMX();
    }
    
    void Processing::newFrame()
    {
        //called periodically from my USB driver
        if(!periodFromUSB)
        {
            periodFromUSB = true;
        }
        myTimer->start(); // restarts current timer interval
    
        processDMX();
    
        // for debugging: display actual interval length in milliseconds:
        static qint64 start = QDateTime::currentMSecsSinceEpoch();
        static qint64 count = 0;
        ++count;
        qint64 curr = QDateTime::currentMSecsSinceEpoch();
        float msecsPerInterval = float(curr - start) / float(count);
        qDebug() << "msecs per interval: " << msecsPerInterval;
    }
    
    void Processing::init()
    {
        myTimer = new QTimer();
        connect(myTimer, &QTimer::timeout, this, &Processing::newFrame);
        myTimer->setInterval(FRAME_INTERVAL_MSEC);
        myTimer->start();
    }
    

    If your localTimeout() slot still gets called, your FRAME_INTERVAL_MSEC is too low.

    You can check this by using the debugging code I added to newFrame().

    This can be because:

    • your USB driver receives data too seldom or
    • the processing in processDMX() takes too long (if the duration of processDMX() is longer than FRAME_INTERVAL_MSEC, the next slot doesn't get called in time)