4

I have an app that reads environmental data from a USB sensor connected to a Mac. Users are able to configure how often the app samples data and how often the app averages those samples and logs the average to a file.

I first used NSTimer but that was wildly inaccurate, especially when the display went to sleep. I am now using a DispatchSourceTimer but it is still losing 1 millisecond about every 21-23 seconds which is about 1 second every 6 hours or so. I'd ideally like that to be less than 1 second per day.

Any ideas how I can tune in the timer to be a little more accurate?

func setupTimer() -> DispatchSourceTimer {
    let timer = DispatchSource.makeTimerSource(flags: .strict, queue: nil)
    let repeatInterval = DispatchTimeInterval.seconds(samplingInterval)
    let deadline : DispatchTime = .now() + repeatInterval
    timer.schedule(deadline: deadline, repeating: repeatInterval, leeway: .nanoseconds(0))
    timer.setEventHandler(handler: self.collectPlotAndLogDatapoint)
    return timer
}

func collectPlotAndLogDatapoint() {
    samplingIntervalCount += 1
    let dataPoint : Float = softwareLoggingDelegate?.getCurrentCalibratedOutput() ?? 0
    accumulatedTotal += dataPoint
    if samplingIntervalCount == loggingInterval / samplingInterval{
        let average = self.accumulatedTotal/Float(self.samplingIntervalCount)
        DispatchQueue.global().async {
            self.logDataPoint(data: average)
            self.chartControls.addPointsToLineChart([Double(average)], Date().timeIntervalSince1970)
            self.samplingIntervalCount = 0
            self.accumulatedTotal = 0
        }
    }
}
Nilanshu Jaiswal
  • 1,583
  • 3
  • 23
  • 34
jgramse
  • 193
  • 10
  • 1
    What about comparing the time in your event handler to the system time as got from `DispatchTime.now()` and adjusting the firing interval accordingly when the drift gets unacceptably large? – JeremyP Nov 20 '18 at 16:41
  • 2
    Just in case, make sure you [disable App Nap](https://stackoverflow.com/questions/50512279/prevent-nstimer-firing-delays-in-background-app/50531545#50531545). – Ken Thomases Nov 20 '18 at 17:05
  • @KenThomases I'm surprised I haven't seen this yet with all my searching how to fix this. Thanks for commenting. It is now disabled while logging is active. – jgramse Nov 20 '18 at 17:29
  • @JeremyP I think that is probably going to be the best approach and it should work well. I was hoping the timer would auto-calibrate itself in some way but I guess that's just wishful thinking. – jgramse Nov 20 '18 at 17:29

1 Answers1

3

The answers (and comments in response) to this seem to suggest that sub-millisecond precision is hard to obtain in Swift:

How do I achieve very accurate timing in Swift?

Apple apparently have their own dos & don'ts for high precision timers: https://developer.apple.com/library/archive/technotes/tn2169/_index.html

~3-4 seconds a day is pretty accurate for an environmental sensor, I'd imagine this would only prove an issue (or even noticeable) for those users who are wanting to take samples on an interval << 1 second.

10623169
  • 984
  • 1
  • 12
  • 22
  • JeremyP's suggestion is also pretty good, if the offset is consistent (i.e. you are drifting 1s/6hr consistently) then you can apply a correction offset every 6 hours – 10623169 Nov 20 '18 at 16:51
  • @zb1955 Yes 3-4 seconds a day is pretty accurate but we make research grade environmental sensors and our customers expect resolution better than this. In a normal consumer type product this wouldn't be an issue at all. I do think I'm going to try JeremyP's suggestion and implement some sort of auto-calibration. Thanks for posting! – jgramse Nov 20 '18 at 17:31