Torc  0.1
torctimercontrol.cpp
Go to the documentation of this file.
1 /* Class TorcTimerControl
2 *
3 * Copyright (C) Mark Kendall 2015-18
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 
20 // Qt
21 #include <QLocale>
22 #include <QVariant>
23 
24 // Torc
25 #include "torclogging.h"
26 #include "torclocalcontext.h"
27 #include "torctimercontrol.h"
28 
29 static const quint64 kThou = 1000;
30 static const quint64 kSixty = 60;
31 static const quint64 k24 = 24;
32 static const quint64 k7 = 7;
33 static const quint64 kMSecsInMinute = kSixty * kThou;
34 static const quint64 kMSecsInHour = kMSecsInMinute * kSixty;
35 static const quint64 kMSecsInDay = kMSecsInHour * k24;
36 static const quint64 kMSecsinWeek = kMSecsInDay * k7;
37 
39 {
40  switch (Type)
41  {
42  case TorcTimerControl::Custom: return QStringLiteral("Custom");
43  case TorcTimerControl::Minutely: return QStringLiteral("Minute");
44  case TorcTimerControl::Hourly: return QStringLiteral("Hourly");
45  case TorcTimerControl::Daily: return QStringLiteral("Daily");
46  case TorcTimerControl::Weekly: return QStringLiteral("Weekly");
47  case TorcTimerControl::SingleShot: return QStringLiteral("SingleShot");
48  default: break;
49  }
50 
51  return QStringLiteral("Unknown");
52 }
53 
55 {
56  QString type = Type.trimmed().toUpper();
57 
58  if ("CUSTOM" == type) return TorcTimerControl::Custom;
59  if ("MINUTELY" == type) return TorcTimerControl::Minutely;
60  if ("HOURLY" == type) return TorcTimerControl::Hourly;
61  if ("DAILY" == type) return TorcTimerControl::Daily;
62  if ("WEEKLY" == type) return TorcTimerControl::Weekly;
63  if ("SINGLESHOT" == type) return TorcTimerControl::SingleShot;
64 
66 }
67 
68 TorcTimerControl::TorcTimerControl(const QString &Type, const QVariantMap &Details)
69  : TorcControl(TorcControl::Timer, Details),
70  m_timerType(StringToTimerType(Type)),
71  m_startDay(0),
72  m_startTime(0),
73  m_duration(0),
74  m_durationDay(0),
75  m_periodDay(0),
76  m_periodTime(0),
77  m_timer(),
78  m_firstTrigger(true),
79  m_randomStart(false),
80  m_randomDuration(false),
81  m_lastElapsed(0),
82  m_newRandom(false),
83  m_active(true),
84  m_singleShotStartTime(0)
85 {
86  static bool randomcheck = true;
87  if (randomcheck)
88  {
89  randomcheck = false;
90  if (RAND_MAX < kMSecsinWeek)
91  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Maximum random number is too low for effective use (%1)").arg(RAND_MAX));
92  }
93 
94  if (m_timerType == TorcTimerControl::UnknownTimerType)
95  {
96  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown timer type '%1' for device '%2'").arg(Details.value(QStringLiteral("type")).toString(), uniqueId));
97  return;
98  }
99 
100  int days, hours, minutes, seconds = 0;
101 
102  // check some details early so we know what elements are required
103  // a custom timer must have a period
104  // start or duration can be random but not both
105  // a single shot timer needs a period if random
106 
107  // check for start time
108  if (!Details.contains(QStringLiteral("start")))
109  {
110  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Timer '%1' does not specify start time").arg(uniqueId));
111  return;
112  }
113 
114  // check duration - format as for start time
115  if (!Details.contains(QStringLiteral("duration")))
116  {
117  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Timer '%1' does not specify duration").arg(uniqueId));
118  return;
119  }
120 
121  // random start time
122  QString start = Details.value(QStringLiteral("start")).toString().toLower().trimmed();
123  m_randomStart = start.contains(QStringLiteral("random"));
124  // random duration
125  QString durations = Details.value(QStringLiteral("duration")).toString().toLower().trimmed();
126  m_randomDuration = durations.contains(QStringLiteral("random"));
127 
128  // a timer cannot have random start and duration
129  if (m_randomStart && m_randomDuration)
130  {
131  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Timer %1 cannot have random start AND duration").arg(uniqueId));
132  return;
133  }
134 
135  bool periodavail = Details.contains(QStringLiteral("period"));
136  bool haveperiod = true;
137  bool random = m_randomStart || m_randomDuration;
138  bool single = TorcTimerControl::SingleShot == m_timerType;
139  m_active = m_firstTrigger = !single;
140 
141  // a custom timer needs a period as does a random single shot
142  if (TorcTimerControl::Custom == m_timerType || (random && single) || (single && !random && periodavail))
143  {
144  if (!periodavail)
145  {
146  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Timer %1 does not specify a period").arg(uniqueId));
147  return;
148  }
149 
150  QString periods = Details.value(QStringLiteral("period")).toString().toLower().trimmed();
151  quint64 periodtime;
152  if (!TorcControl::ParseTimeString(periods, days, hours, minutes, seconds, periodtime))
153  {
154  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse period from '%1'").arg(periods));
155  return;
156  }
157 
158  m_periodDay = days;
159  m_periodTime = periodtime * 1000;
160 
161  // the period must be at least 2 seconds to allow for defined on and off periods
162  if (m_periodTime < 2000)
163  {
164  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Timer %1 has duration of %2seconds - needs at least 2").arg(uniqueId).arg(m_periodTime / 1000));
165  return;
166  }
167  }
168  else if (single && !random)
169  {
170  haveperiod = false;
171  }
172  else
173  {
174  m_periodTime = GetPeriodDuration();
175  m_periodDay = m_periodTime / kMSecsInDay;
176  }
177 
178  // Sanity checks start time and duration
179  // constraints:
180  // - we need a defined off and on period
181  // - the on period can be anywhere in the cycle (start, middle, end)
182  // - the current on period must finish before the next is due to start
183  // - so:
184  // - start >= 0
185  // - start < max
186  // - duration > 0
187  // - duration < max
188  // - should cover all eventualities...
189 
190  if (!m_randomStart)
191  {
192  // start time must be in the format DD:HH:MM or HH:MM or MM with an optional .SS appended
193  quint64 starttime;
194  if (!TorcControl::ParseTimeString(start, days, hours, minutes, seconds, starttime))
195  {
196  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse start time from '%1'").arg(start));
197  return;
198  }
199 
200  m_startDay = days;
201  m_startTime = starttime * 1000;
202 
203  if (haveperiod && (m_startTime >= m_periodTime /*|| m_startTime < 0*/))
204  {
205  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Start time (%2) for %1 is invalid - must be in range 0-%3")
206  .arg(uniqueId).arg(m_startTime / 1000).arg((m_periodTime / 1000) - 1));
207  return;
208  }
209  }
210 
211  if (!m_randomDuration)
212  {
213  quint64 duration;
214  if (!TorcControl::ParseTimeString(durations, days, hours, minutes, seconds, duration))
215  {
216  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse duration from '%1'").arg(durations));
217  return;
218  }
219 
220  m_duration = duration * 1000;
221  m_durationDay = days;
222 
223  if ((m_duration < 1000) || (haveperiod && (m_duration >= m_periodTime)))
224  {
225  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Duration (%1) for %2 is invalid - must be in range 1-%3")
226  .arg(m_duration / 1000).arg(uniqueId).arg((m_periodTime / 1000) - 1));
227  return;
228  }
229  }
230 
231  // period is defined as start plus duration
232  if (!haveperiod)
233  {
234  m_periodTime = m_startTime + m_duration;
235  if (m_periodTime < 2000) // minimum 1 sec on - 1 sec off
236  m_periodTime = 2000;
237  m_periodDay = m_periodTime / kMSecsInDay;
238  }
239 
240  // generate random start/duration if necessary
241  GenerateTimings();
242 
243  // everything appears to be valid at this stage
244  m_parsed = true;
245 
246  gLocalContext->AddObserver(this);
247 }
248 
250 {
252 }
253 
255 {
256  return TorcControl::Timer;
257 }
258 
260 {
261  QStringList result;
262 
263  result.append(TimerTypeToString(m_timerType));
264  result.append(tr("Period") + ": " + TorcControl::DurationToString(m_periodDay, m_periodTime / 1000));
265  result.append(tr("Start") + ": " + (m_randomStart ? tr("Random") : TorcControl::DurationToString(m_startDay, m_startTime / 1000)));
266  result.append(tr("Duration") + ": " + (m_randomDuration ? tr("Random") : TorcControl::DurationToString(m_durationDay, m_duration / 1000)));
267  return result;
268 }
269 
271 {
272  QMutexLocker locker(&lock);
273 
274  // don't repeat validation
275  if (m_validated)
276  return true;
277 
278  // common checks
279  if (!TorcControl::Validate())
280  return false;
281 
282  // a timer has no inputs (apart from singleShot)
283  if ((m_timerType == TorcTimerControl::SingleShot) && m_inputs.isEmpty())
284  {
285  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Time device '%1' needs an input").arg(uniqueId));
286  return false;
287  }
288  else if ((m_timerType != TorcTimerControl::SingleShot) && !m_inputs.isEmpty())
289  {
290  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Timer device '%1' cannot have inputs").arg(uniqueId));
291  return false;
292  }
293 
294  // need at least one output
295  if (m_outputs.isEmpty())
296  {
297  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Timer device '%1' needs at least one output").arg(uniqueId));
298  return false;
299  }
300 
301  // if we get this far, we can finish the device
302  if (!Finish())
303  return false;
304 
305  // debug
306  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Timer '%1': %2").arg(uniqueId, GetDescription().join(',')));
307 
308  m_timer.setTimerType(Qt::PreciseTimer);
309 
310  // connect the timer
311  connect(&m_timer, &QTimer::timeout, this, &TorcTimerControl::TimerTimeout);
312 
313  // all QTimer's are single shot and reset at each trigger
314  m_timer.setSingleShot(true);
315 
316  return true;
317 }
318 
325 {
326  QMutexLocker locker(&lock);
327 
328  if (!m_parsed || !m_validated)
329  return;
330 
331  if (m_active)
332  {
333  // trigger the timer for the next state change
334  TimerTimeout();
335 
336  // ensure state is communicated
337  SetValid(true);
338  emit ValueChanged(value);
339  }
340 }
341 
344 {
345  return TorcTimerControl::SingleShot == m_timerType;
346 }
347 
348 bool TorcTimerControl::event(QEvent *Event)
349 {
350  if (Event && Event->type() == TorcEvent::TorcEventType)
351  {
352  TorcEvent *event = static_cast<TorcEvent*>(Event);
353  if (event && (event->GetEvent() == Torc::SystemTimeChanged))
354  {
355  if (TorcTimerControl::SingleShot == m_timerType)
356  {
357  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Not restarting single shot timer %1").arg(uniqueId));
358  }
359  else
360  {
361  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Timer %1 restarting").arg(uniqueId));
362  m_timer.stop();
363  m_firstTrigger = true;
364  TimerTimeout();
365  }
366  return true;
367  }
368  }
369 
370  return TorcControl::event(Event);
371 }
372 
374 {
375  QMutexLocker locker(&lock);
376 
377  if (!m_active)
378  return;
379 
380  bool first = m_firstTrigger;
381  m_firstTrigger = false;
382 
383  if (TorcTimerControl::UnknownTimerType == m_timerType)
384  return;
385 
386  quint64 msecsinceperiodstart = MsecsSincePeriodStart();
387  quint64 finishtime = m_startTime + m_duration;
388  bool newvalue = false;
389  quint64 nexttimer = 0;
390 
391  if (TorcTimerControl::SingleShot == m_timerType)
392  {
393  // single shot much easier to handle
394  if (msecsinceperiodstart > finishtime)
395  {
396  m_active = false;
397  newvalue = false;
398  }
399  else if (msecsinceperiodstart < m_startTime)
400  {
401  newvalue = false;
402  nexttimer = m_startTime - msecsinceperiodstart;
403  }
404  else
405  {
406  newvalue = true;
407  nexttimer = finishtime - msecsinceperiodstart;
408  }
409  }
410  else if (TorcTimerControl::Custom == m_timerType ||
411  TorcTimerControl::Minutely == m_timerType ||
412  TorcTimerControl::Hourly == m_timerType ||
413  TorcTimerControl::Daily == m_timerType ||
414  TorcTimerControl::Weekly == m_timerType)
415  {
416  // we need to establish the current time, the current state
417  // and when the next trigger is needed.
418  // A timer that does not cross a period boundary has a pattern of
419  // off/on, off/on/off or on/off. '_-' '_-_' '-_'
420  // A timer that DOES cross a boundary will have a pattern of
421  // off/on/boundary/on/off '_-|-_' for its total cycle but within
422  // the period it looks like '-_-' for the two overlapping sequences
423  // so we have 4 total combinations to handle...
424 
425  bool newrandom = false;
426 
427  // start is zero
428  // on/off -_
429  if (m_startTime == 0)
430  {
431  newvalue = msecsinceperiodstart <= m_duration;
432  nexttimer = newvalue ? m_duration - msecsinceperiodstart : m_periodTime - msecsinceperiodstart;
433  newrandom = value < 1.0 && newvalue; // transitioning from low to high
434  }
435  // start + duration < max
436  // off/on/off _-_
437  else if (finishtime < m_periodTime)
438  {
439  if (msecsinceperiodstart < m_startTime)
440  {
441  newvalue = false;
442  nexttimer = m_startTime - msecsinceperiodstart;
443  }
444  else if (msecsinceperiodstart > finishtime)
445  {
446  newvalue = false;
447  nexttimer = m_periodTime - msecsinceperiodstart;
448  }
449  else
450  {
451  // m_startTime <= timesinceperiodstart <= finishtime
452  newvalue = true;
453  nexttimer = finishtime - msecsinceperiodstart;
454  }
455 
456  if (!newvalue && (value < 1.0) && (m_lastElapsed > msecsinceperiodstart)) // end of period boundary
457  newrandom = true;
458  }
459  // start + duration = max
460  // off/on _-
461  else if (finishtime == m_periodTime)
462  {
463  newvalue = msecsinceperiodstart >= m_startTime;
464  nexttimer = newvalue ? m_periodTime - msecsinceperiodstart : m_startTime - msecsinceperiodstart;
465  newrandom = value > 0.0 && !newvalue; // transitioning from high to low
466  }
467  // on/off/on -_-
468  else if (finishtime > m_periodTime)
469  {
470  // a random timer should never hit this condition as it becomes too involved
471  // trying to trigger the correct timeouts
472  if (m_randomDuration || m_randomStart)
473  {
474  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid condition for random timer - disabling"));
475  return;
476  }
477 
478  quint64 firststart = finishtime - m_periodTime;
479 
480  if (msecsinceperiodstart <= firststart)
481  {
482  newvalue = true;
483  nexttimer = firststart - msecsinceperiodstart;
484  }
485  else if (msecsinceperiodstart >= m_startTime)
486  {
487  newvalue = true;
488  nexttimer = m_periodTime - msecsinceperiodstart;
489  }
490  else
491  {
492  newvalue = false;
493  nexttimer = m_startTime - msecsinceperiodstart;
494  }
495  }
496 
497  if (first && newvalue)
498  {
499  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Triggering timer '%1' late - will run for %2seconds instead of %3 %4")
500  .arg(uniqueId).arg(nexttimer/1000.0).arg(m_duration/1000).arg(QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss.zzz"))));
501  }
502 
503  m_lastElapsed = msecsinceperiodstart;
504 
505  // the end of the 'random' sequence. Calculate next sequence and then
506  // recalculate next timeout. Don't set the value as it may momentarily be set to the incorrect value.
507  // m_newRandom prevents a potential infinite loop as a new random start/duration may immediately
508  // trigger another. Don't reset random on first trigger - it has only just been set!
509  if (!first && newrandom && !m_newRandom && (m_randomDuration || m_randomStart))
510  {
511  m_newRandom = true;
512  GenerateTimings();
513  TimerTimeout();
514  return;
515  }
516 
517  m_newRandom = false;
518  }
519 
520  if (m_active)
521  {
522  // trigger timer a little early to hone in and give sub-second accuracy
523  quint64 adjust = nexttimer / 10;
524  if (nexttimer > 100)
525  nexttimer -= adjust;
526 
527  // avoid lengthy timers - max 1 hour
528  if (nexttimer > kMSecsInHour)
529  nexttimer = kMSecsInHour;
530 
531  m_timer.start(nexttimer);
532  }
533  SetValue(newvalue);
534 }
535 
537 {
538  return m_timerType;
539 }
540 
542 {
543  QMutexLocker locker(&lock);
544 
545  if (!m_active)
546  return std::numeric_limits<quint64>::max();
547 
548  if (TorcTimerControl::SingleShot == m_timerType)
549  return m_startTime ? std::numeric_limits<quint64>::max() : 0;
550 
551  quint64 msecsinceperiodstart = MsecsSincePeriodStart();
552  quint64 finishtime = m_startTime + m_duration;
553  quint64 lasttimer = 0;
554 
555  // N.B. There can only ever be two 'reference' transitions - the last low to high
556  // and the last high to low. A boundary is NOT a transition (unless coincidental).
557 
558  // start is zero
559  // on/off -_
560  if (m_startTime == 0)
561  {
562  lasttimer = (msecsinceperiodstart <= m_duration) ? msecsinceperiodstart : msecsinceperiodstart - m_duration;
563  }
564  // start + duration < max
565  // off/on/off _-_
566  else if (finishtime < m_periodTime)
567  {
568  if (msecsinceperiodstart < m_startTime)
569  {
570  lasttimer = msecsinceperiodstart + (m_periodTime - finishtime);
571  }
572  else if (msecsinceperiodstart > finishtime)
573  {
574  lasttimer = msecsinceperiodstart - finishtime;
575  }
576  else
577  {
578  lasttimer = msecsinceperiodstart - m_startTime;
579  }
580  }
581  // start + duration = max
582  // off/on _-
583  else if (finishtime == m_periodTime)
584  {
585  lasttimer = (msecsinceperiodstart >= m_startTime) ? msecsinceperiodstart - m_startTime : msecsinceperiodstart;
586  }
587  // on/off/on -_-
588  else if (finishtime > m_periodTime)
589  {
590  quint64 firststart = finishtime - m_periodTime;
591  if (msecsinceperiodstart <= firststart)
592  {
593  lasttimer = msecsinceperiodstart + (m_periodTime - m_startTime);
594  }
595  else if (msecsinceperiodstart >= m_startTime)
596  {
597  lasttimer = msecsinceperiodstart - m_startTime;
598  }
599  else
600  {
601  lasttimer = msecsinceperiodstart - firststart;
602  }
603  }
604 
605  return lasttimer;
606 }
607 
608 quint64 TorcTimerControl::MsecsSincePeriodStart(void)
609 {
610  if (!m_active)
611  return 0;
612 
613  QTime timenow = QTime::currentTime();
614  int day = QDate::currentDate().dayOfWeek() - 1;
615 
616  if (TorcTimerControl::Custom == m_timerType)
617  {
618  // always use the same reference Date/Time to ensure long duration custom timers start consistently
619  // and to prevent drift
620  static const QDateTime reference = QDateTime::fromString(QStringLiteral("2000-01-01T00:00:00"), Qt::ISODate);
621  if (m_periodTime > 0)
622  return (QDateTime::currentMSecsSinceEpoch() - reference.toMSecsSinceEpoch()) % m_periodTime;
623  return 0;
624  }
625  else if (TorcTimerControl::SingleShot == m_timerType)
626  {
627  return QDateTime::currentMSecsSinceEpoch() - m_singleShotStartTime;
628  }
629 
630  // clip hours for Hourly
631  if (TorcTimerControl::Hourly == m_timerType)
632  timenow = QTime(0, timenow.minute(), timenow.second(), timenow.msec());
633  // clip hours and minutes for minutely
634  else if (TorcTimerControl::Minutely == m_timerType)
635  timenow = QTime(0, 0, timenow.second(), timenow.msec());
636 
637  quint64 msecsinceperiodstart = timenow.msecsSinceStartOfDay();
638  // add days for Weekly
639  if (TorcTimerControl::Weekly == m_timerType)
640  msecsinceperiodstart += day * kMSecsInDay;
641  return msecsinceperiodstart;
642 }
643 
644 quint64 TorcTimerControl::GetPeriodDuration(void) const
645 {
646  switch (m_timerType)
647  {
652  default:
653  break;
654  }
655  return 0;
656 }
657 
661 void TorcTimerControl::GenerateTimings(void)
662 {
663  // start or duration are limited in range as we must keep the cycle within the period
664  if (m_randomDuration)
665  {
666  // for a 60 second timer, we need duration between 1 and 59 - a range of 58
667  // start is already 0-59 - a range of 59...
668  // if we clip the result, we lose the random distribution
669  // so we need to scale down the start time to restrict the range
670  // which means we need to operate to ms accuracy and round down
671  quint64 start = (quint64)((double)m_startTime * ((double)(m_periodTime - 1000) / (double)m_periodTime));
672  quint64 mod = (m_periodTime / 1000) - (start / 1000); // range 2<->period
673  mod -= 1; // range 1<->(period-1)
674  m_duration = (qrand() % mod); // range 0<->period-2
675  m_duration += 1; // range 1<->period-1
676  m_duration *= 1000;
677  m_durationDay = m_duration / kMSecsInDay;
678  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Timer %1 - new random duration %2").arg(uniqueId, DurationToString(m_durationDay, m_duration / 1000)));
679  }
680  else if (m_randomStart)
681  {
682  quint64 mod = (m_periodTime / 1000) - (m_duration / 1000) + 1; // range 2<->period
683  m_startTime = qrand() % (mod); // range 0<->period-1
684  m_startTime *= 1000;
685  m_startDay = m_startTime / kMSecsInDay;
686  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Timer %1 - new random start %2").arg(uniqueId, DurationToString(m_startDay, m_startTime / 1000)));
687  }
688 }
689 
690 void TorcTimerControl::CalculateOutput(void)
691 {
692  if (TorcTimerControl::SingleShot != m_timerType)
693  return;
694 
695  {
696  QMutexLocker locker(&lock);
697  // always start/restart when the input transitions low to high
698  if (m_lastInputValues.constBegin().value() < 1.0 && m_inputValues.constBegin().value() >= 1.0)
699  {
700  if (m_active)
701  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Single shot timer %1 restarting").arg(uniqueId));
702  m_active = true;
703  GenerateTimings();
704  m_singleShotStartTime = QDateTime::currentMSecsSinceEpoch();
705  TimerTimeout();
706  }
707  }
708 }
static QString TimerTypeToString(TorcTimerControl::TimerType Type)
static TorcTimerControl::TimerType StringToTimerType(const QString &Type)
static bool ParseTimeString(const QString &Time, int &Days, int &Hours, int &Minutes, int &Seconds, quint64 &DurationInSeconds)
Parse a Torc time string into days, hours, minutes and, if present, seconds.
Definition: torccontrol.cpp:36
bool AllowInputs(void) const override
Timers cannot have inputs.
void ValueChanged(double Value)
TorcLocalContext * gLocalContext
virtual bool Validate(void)
static const quint64 k24
QString uniqueId
Definition: torcdevice.h:63
static const quint64 kMSecsinWeek
QMap< QObject *, QString > m_inputs
Definition: torccontrol.h:71
static const quint64 kMSecsInMinute
bool m_validated
Definition: torccontrol.h:68
static const quint64 kThou
QMap< QObject *, double > m_inputValues
Definition: torccontrol.h:73
bool Validate(void) override
QMap< QObject *, double > m_lastInputValues
Definition: torccontrol.h:74
bool m_parsed
Definition: torccontrol.h:67
void SetValid(bool Valid) override
TorcTimerControl::TimerType GetTimerType(void) const
VERBOSE_PREAMBLE true
static const quint64 kMSecsInDay
quint64 TimeSinceLastTransition(void)
void SetValue(double Value) override
QMutex lock
Definition: torcdevice.h:66
static Type TorcEventType
Register TorcEventType with QEvent.
Definition: torcevent.h:19
TorcTimerControl(const QString &Type, const QVariantMap &Details)
static QString DurationToString(int Days, quint64 Duration)
double value
Definition: torcdevice.h:60
bool Finish(void)
Finish setup of the control.
A general purpose event object.
Definition: torcevent.h:9
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
QStringList GetDescription(void) override
void AddObserver(QObject *Observer)
brief Register the given object to receive events.
static const quint64 kMSecsInHour
TorcControl::Type GetType(void) const override
void Start(void) override
Initialise the timer.
void RemoveObserver(QObject *Observer)
brief Deregister the given object.
static const quint64 k7
bool event(QEvent *Event) override
static const quint64 kSixty
QMap< QObject *, QString > m_outputs
Definition: torccontrol.h:72