PDA

View Full Version : Suggestions on how to make an alarmclock-like function



Tottish
3rd March 2011, 20:12
Hi!
I'd like some suggestions from more experienced programmers on how to do this the right way. Let me first make clear that I could do this on my own but I'd like the opinions of others to choose more specifically how to go about it.

What I need is for a program that is running on a server to write a report and send out by email upon a number of preset clock strikes scattered throughout the day at uneven intervals.

This might seem like a mundane enough task but I have yet to find a solution that doesn't feel awkward.

My current solution is a QList<QTimers> with timers that are initialized in the ctor of the program and are singleshot-started but it's rather bulky code and when the timeout-signal is shot the calling timer should simply be set to 24 hours (triggering the same time the day after) and then all is up'n'running but it feel like I'm overlooking some simpler solution?

It feels like a rather common behavior and I've gotten so used to the convenience of the Qt framework I'm actually half expecting there to be a QAlarmClock where I can just pass a QList of QTime's or QTimeDate's and, presto! =)

Any takers?
Thanks
/Tottish

wysota
6th March 2011, 09:44
You mean you wish to have something that will notify you at given time?
This should work:

class AlarmClock : public QObject {
public:
AlarmClock(QObject *parent = 0) : QObject(parent), m_timer(0) {}
public slots:
void start() {
if(m_timer!=0) return;
m_timer = startTimer(1000); // 1s resolution
}
void stop() {
if(m_timer==0) return;
killTimer(m_timer);
m_timer = 0;
}
void addAlarm(const QDateTime &dt) {
QList<QDateTimer>::iterator iter = qLowerBound(m_alarms.begin(), m_alarms.end, dt);
if(*iter != dt) m_alarms.insert(iter, dt);
}
signals:
void alarm(const QDateTime &dt);
protected:
void timerEvent(QTimerEvent *ev) {
if(ev->timerId()!=m_timer) { QObject::timerEvent(ev); return; }
QDateTime current = QDateTime::currentDateTime();
while(m_alarms.first()<=current) {
emit alarm(m_alarms.first());
m_alarms.removeFirst();
}
}
private:
QList<QDateTime> m_alarms;
int m_timer;
};


AlarmClock *alarms = new AlarmClock(this);
alarms->addAlarm(QDateTime(QDate::currentDate(), QTime(10, 46, 53)));
alarms->start();
alarms->addAlarm(QDateTime(QDate::currentDate().addDays(1) , QTime(9, 12, 00)));
connect(alarms, SIGNAL(alarm(QDateTime)), ..., ...);

Tottish
6th March 2011, 11:14
Yeah, that looks like it should do it! It seems like it is working with daytimes rather than times which I would prefer since the alarms should be at the same time every day of the week. But hopefully it's just a minor tweak. =)
I'll look into the code to get a deeper understanding on what it really does tomorrow or maybe the day after that since I've got my hands full of other stuff right now.

Again; A very big 'Thank You', wysota!

Cheers!
/Tottish

wysota
6th March 2011, 13:35
The problem with using only QTime is that if you have an alarm set to, say... 14:56 and the check is done at 14:57 then you don't know whether the 14:56 alarm was already triggered or not (in other words is it "today's" 14:56 or "tomorrow's"). Consistency demands that when you "miss" an alarm, you should fire it as soon as possible to "catch up" with the schedule. This situation can happen in practise when system is suspended, under heavy load, during synchronization with an NTP server or during change of timezone when daylight saving happens (then the same event might fire twice). You can easily have an off-by-one miss, especially that timerEvent() is triggered at approximately 1000ms and not exactly 1000ms (so it can fire at 14:55:59.999 and then at 14:56:01.001 completely skipping the 14:56:00 second). It is much better to schedule a new alarm at the end of handling the previous one. Then you can decide "on the fly" when the next alarm should occur.

Tottish
6th March 2011, 13:57
OK, I am actually having kind of the same problem right now for my temporary function. This is a really dirty one but a QTimer in the program is actually firing a signal every second for updating purposes so, in the connected slot, I'm just comparing QTime::CurrentTime with a bunch of preset values and send a report ("alarm") if the comparison is true.
It actually misses quite a lot of reports which, as you are pointing out wysota, is to be expected because the program does a whole bunch of stuff other than setting the QTimer to 1000.

Point taken. I'll set the timers on the fly.

Thanks again!
/Tottish

wysota
6th March 2011, 15:53
According to some signal-processing laws you could set the timer to 500ms to have a properly working 1000ms clock. Of course this wouldn't help in situations when intermediate processing takes longer than 500ms or when the system clock is changed by outside source (suspension, ntp, daylight saving, manual user intervention). I think my solution is more foul-proof.

Tottish
8th March 2011, 10:19
I think you're right, it sounds more convenient too. I'm back at the computer now so I'll get right on it!
Cheers!
/Tottish

Tottish
9th March 2011, 10:36
So after some time I got it to work.

I spent quite a bit of time trying to get the iterator in addAlarm to cooperate but I never quite got the hang of it so I did it in a way that seemed more familiar to me. Please let me know if my way is less suitable or if I misunderstood the purpose of the iterator.

I also spent some time scratching my head before I realized why the program crashed if the alarm list goes empty. But it seems that that's what happens if one tries to read from list.first in an empty list so I built in protection for that too. Other than that it's pretty much the same as what wysota posted above.

Should anyone else be building an alarm clock or timer of some sort, here is my version (I'm not guaranteeing anything but it seems pretty stable):


#ifndef REPORTTIMER_H
#define REPORTTIMER_H

#include <QtCore>

class ReportTimer : public QObject {
Q_OBJECT

public:
ReportTimer(QObject *parent = 0) : QObject(parent), m_timer(0) {}
public slots:
void start() {
if(m_timer!=0) return;
m_timer = startTimer(4000); // 4s resolution
}

void stop() {
if(m_timer==0) return;
killTimer(m_timer);
m_timer = 0;
}

void addAlarm(const QDateTime &dt) {
qDebug() << "in";
if (m_alarms.indexOf(dt) != -1) return; //if already in list, return
else {m_alarms.append(dt); qSort(m_alarms);}
}

signals:
void alarm(const QDateTime &dt);

protected:
void timerEvent(QTimerEvent *ev) { qDebug() << "Fired" << m_alarms;
if(ev->timerId()!=m_timer) { QObject::timerEvent(ev); return; }
QDateTime current = QDateTime::currentDateTime();
while(m_alarms.isEmpty() == false && m_alarms.first() <= current) {
emit alarm(m_alarms.first());
m_alarms.removeFirst();
}
}


private:
QList<QDateTime> m_alarms;
int m_timer;
};

#endif // REPORTTIMER_H


Cheers!
/Tottish

wysota
9th March 2011, 12:47
I don't get why you replaced what I had with your line #26. It's suboptimal. The code just had two typos :) After correcting them it compiles and works fine. Here is the corrected code:

#include <QtGui>
#include <QObject>

class AlarmClock : public QObject {
Q_OBJECT
public:
AlarmClock(QObject *parent = 0) : QObject(parent), m_timer(0) {}
public slots:
void start(int ms = 1000) {
if(m_timer!=0) return;
m_timer = startTimer(ms); // 1s resolution
}
void stop() {
if(m_timer==0) return;
killTimer(m_timer);
m_timer = 0;
}
void addAlarm(const QDateTime &dt) {
QList<QDateTime>::iterator iter = qLowerBound(m_alarms.begin(), m_alarms.end(), dt);
if(iter==m_alarms.end() || *iter != dt) m_alarms.insert(iter, dt);
}
signals:
void alarm(const QDateTime &dt);
protected:
void timerEvent(QTimerEvent *ev) {
if(ev->timerId()!=m_timer) { QObject::timerEvent(ev); return; }
QDateTime current = QDateTime::currentDateTime();
while(!m_alarms.isEmpty() && m_alarms.first()<=current) {
emit alarm(m_alarms.first());
m_alarms.removeFirst();
}
}
private:
QList<QDateTime> m_alarms;
int m_timer;
};

class Dummy : public QObject {
Q_OBJECT
public slots:
void onAlarm(const QDateTime &dt) {
qDebug() << dt;
}
};

#include "main.moc"

int main(int argc, char **argv){
QApplication app(argc, argv);
AlarmClock alarms;
Dummy d;
d.connect(&alarms, SIGNAL(alarm(QDateTime)), &d, SLOT(onAlarm(QDateTime)));
alarms.addAlarm(QDateTime(QDate::currentDate(), QTime::currentTime().addSecs(2)));
alarms.addAlarm(QDateTime(QDate::currentDate(), QTime::currentTime().addSecs(6)));
alarms.addAlarm(QDateTime(QDate::currentDate(), QTime::currentTime().addSecs(14)));
alarms.addAlarm(QDateTime(QDate::currentDate(), QTime::currentTime().addSecs(10)));
alarms.start();
return app.exec();
}

Tottish
9th March 2011, 12:59
Well, as I said, I changed it to a syntax that I felt more familiar with since I couldn't get the iterator to work/finding and correcting the "typo" =).
"Suboptimal", I'm assuming you mean that the functionality/behavior is the same but your iterator way is faster? I'll take your word for it. *Changing code*
In any case it was nice to find a solution on my own. Suboptimal as it may be. =)
Thanks wysota!
Have an awesome day!
/Tottish

wysota
9th March 2011, 13:07
The difference is you are resorting the list when adding a new element while in fact the list is already sorted with exception of one element. My solution just looks for the proper place to insert the element in an already sorted array so it's faster. The use of iterators is irrelevant here, they are there only because there is no qLowerBound() version that returns an index.

Tottish
9th March 2011, 13:09
OK, I see. Thank you for the clarification!
/Tottish