dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
notifyplugin.cpp
Go to the documentation of this file.
1 
15 /*
16  * This program is free software; you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 3 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful, but
22  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
23  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24  * for more details.
25  *
26  * You should have received a copy of the GNU General Public License along
27  * with this program; if not, see <http://www.gnu.org/licenses/>
28  */
29 
30 #include "notifyplugin.h"
31 #include "notificationitem.h"
33 #include "notifylogging.h"
34 #include <coreplugin/icore.h>
35 #include <QDebug>
36 #include <QtPlugin>
37 #include <QStringList>
38 
40 
41 #include <iostream>
42 
43 static const QString VERSION = "1.0.0";
44 
45 //#define DEBUG_NOTIFIES
46 
47 SoundNotifyPlugin::SoundNotifyPlugin()
48 {
49  phonon.mo = NULL;
50 }
51 
52 SoundNotifyPlugin::~SoundNotifyPlugin()
53 {
55  if (phonon.mo != NULL)
56  delete phonon.mo;
57 }
58 
59 bool SoundNotifyPlugin::initialize(const QStringList &args, QString *errMsg)
60 {
61  Q_UNUSED(args);
62  Q_UNUSED(errMsg);
63 
64  mop = new NotifyPluginOptionsPage(this);
66 
67  return true;
68 }
69 
70 void SoundNotifyPlugin::extensionsInitialized()
71 {
73 
74  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
76  &SoundNotifyPlugin::onTelemetryManagerAdded);
77  _toRemoveNotifications.clear();
78  connectNotifications();
79 }
80 
81 void SoundNotifyPlugin::saveConfig(QSettings *settings, UAVConfigInfo *configInfo)
82 {
83 
84  configInfo->setVersion(VERSION);
85 
86  settings->beginWriteArray("Current");
87  settings->setArrayIndex(0);
88  currentNotification.saveState(settings);
89  settings->endArray();
90 
91  settings->beginGroup("listNotifies");
92  settings->remove("");
93  settings->endGroup();
94 
95  settings->beginWriteArray("listNotifies");
96  for (int i = 0; i < _notificationList.size(); i++) {
97  settings->setArrayIndex(i);
98  _notificationList.at(i)->saveState(settings);
99  }
100  settings->endArray();
101  settings->setValue(QLatin1String("EnableSound"), enableSound);
102 }
103 
104 void SoundNotifyPlugin::readConfig(QSettings *settings, UAVConfigInfo * /* configInfo */)
105 {
106  // Just for migration to the new format.
107  // Q_ASSERT(configInfo->version() == UAVConfigVersion());
108 
109  settings->beginReadArray("Current");
110  settings->setArrayIndex(0);
111  currentNotification.restoreState(settings);
112  settings->endArray();
113 
114  // read list of notifications from settings
115  int size = settings->beginReadArray("listNotifies");
116  for (int i = 0; i < size; ++i) {
117  settings->setArrayIndex(i);
118  NotificationItem *notification = new NotificationItem;
119  notification->restoreState(settings);
120  _notificationList.append(notification);
121  }
122  settings->endArray();
123  setEnableSound(settings->value(QLatin1String("EnableSound"), 0).toBool());
124 }
125 
126 void SoundNotifyPlugin::onTelemetryManagerAdded(QObject *obj)
127 {
128  telMngr = qobject_cast<TelemetryManager *>(obj);
129  if (telMngr)
130  connect(telMngr, &TelemetryManager::disconnected, this,
131  &SoundNotifyPlugin::onAutopilotDisconnect);
132 }
133 
134 void SoundNotifyPlugin::shutdown()
135 {
136  // Do nothing
137 }
138 
139 void SoundNotifyPlugin::onAutopilotDisconnect()
140 {
141  connectNotifications();
142 }
143 
148 void SoundNotifyPlugin::resetNotification(void)
149 {
150  // first, reject empty args and unknown fields.
151  foreach (NotificationItem *ntf, _notificationList) {
152  ntf->disposeTimer();
153  disconnect(ntf->getTimer(), &QTimer::timeout, this,
154  &SoundNotifyPlugin::on_timerRepeated_Notification);
155  ntf->disposeExpireTimer();
156  disconnect(ntf->getExpireTimer(), &QTimer::timeout, this,
157  &SoundNotifyPlugin::on_timerRepeated_Notification);
158  }
159 }
160 
166 {
167  _toRemoveNotifications.clear();
168  resetNotification();
169  _notificationList.clear();
170  _notificationList = list;
171  connectNotifications();
172 
174 }
175 
176 void SoundNotifyPlugin::connectNotifications()
177 {
178  foreach (UAVDataObject *obj, lstNotifiedUAVObjects) {
179  if (obj != NULL)
180  disconnect(obj, &UAVObject::objectUpdated, this,
181  &SoundNotifyPlugin::on_arrived_Notification);
182  }
183  if (phonon.mo != NULL) {
184  delete phonon.mo;
185  phonon.mo = NULL;
186  }
187 
188  if (!enableSound)
189  return;
190 
191  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
192  UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
193 
194  lstNotifiedUAVObjects.clear();
195  _pendingNotifications.clear();
196  _notificationList.append(_toRemoveNotifications);
197  _toRemoveNotifications.clear();
198 
199  // first, reject empty args and unknown fields.
200  foreach (NotificationItem *notify, _notificationList) {
201  notify->_isPlayed = false;
202  notify->isNowPlaying = false;
203 
204  if (notify->mute())
205  continue;
206  // check is all sounds presented for notification,
207  // if not - we must not subscribe to it at all
208  if (notify->toSoundList().isEmpty())
209  continue;
210 
211  UAVDataObject *obj =
212  dynamic_cast<UAVDataObject *>(objManager->getObject(notify->getDataObject()));
213  if (obj != NULL) {
214  if (!lstNotifiedUAVObjects.contains(obj)) {
215  lstNotifiedUAVObjects.append(obj);
216 
217  connect(obj, &UAVObject::objectUpdated, this,
218  &SoundNotifyPlugin::on_arrived_Notification, Qt::QueuedConnection);
219  }
220  } else {
221  qNotifyDebug() << "Error: Object is unknown (" << notify->getDataObject() << ").";
222  }
223  }
224 
225  if (_notificationList.isEmpty())
226  return;
227  // set notification message to current event
228  phonon.mo = new QMediaPlayer;
229  phonon.firstPlay = true;
230  connect(phonon.mo, &QMediaPlayer::stateChanged, this, &SoundNotifyPlugin::stateChanged);
231 }
232 
233 void SoundNotifyPlugin::on_arrived_Notification(UAVObject *object)
234 {
235  foreach (NotificationItem *ntf, _notificationList) {
236  if (object->getName() != ntf->getDataObject())
237  continue;
238 
239  // skip duplicate notifications
240  if (_nowPlayingNotification == ntf)
241  continue;
242 
243  // skip periodical notifications
244  // this condition accepts:
245  // 1. Periodical notifications played firstly;
246  // NOTE: At first time it will be played, then it played only by timer,
247  // when conditions became false firstStart flag has been cleared and
248  // notification can be accepted again;
249  // 2. Once time notifications, they removed immediately after first playing;
250  // 3. Instant notifications(played one by one without interval);
254  continue;
255 
256  qNotifyDebug() << QString("new notification: | %1 | %2 | val1: %3 | val2: %4")
257  .arg(ntf->getDataObject())
258  .arg(ntf->getObjectField())
259  .arg(ntf->singleValue().toString())
260  .arg(ntf->valueRange2());
261 
262  checkNotificationRule(ntf, object);
263  }
264  connect(object, &UAVObject::objectUpdated, this, &SoundNotifyPlugin::on_arrived_Notification,
265  Qt::UniqueConnection);
266 }
267 
268 void SoundNotifyPlugin::on_timerRepeated_Notification()
269 {
270  NotificationItem *notification = static_cast<NotificationItem *>(sender()->parent());
271  if (!notification)
272  return;
273  // skip duplicate notifications
274  // WARNING: generally we shoudn't ever trap here
275  // this means, that timer fires to early and notification overlap itself
276  if (_nowPlayingNotification == notification) {
277  qNotifyDebug() << "WARN: on_timerRepeated - notification was skipped!";
278  notification->restartTimer();
279  return;
280  }
281 
282  qNotifyDebug() << QString("repeatTimer: %1% | %2 | %3")
283  .arg(notification->getDataObject())
284  .arg(notification->getObjectField())
285  .arg(notification->toString());
286 
287  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
288  UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
289  UAVObject *object = objManager->getObject(notification->getDataObject());
290  if (object)
291  checkNotificationRule(notification, object);
292 }
293 
294 void SoundNotifyPlugin::on_expiredTimer_Notification()
295 {
296  // fire expire timer
297  NotificationItem *notification = static_cast<NotificationItem *>(sender()->parent());
298  if (!notification)
299  return;
300  notification->stopExpireTimer();
301 
302  if (!_pendingNotifications.isEmpty()) {
303  qNotifyDebug() << QString("expireTimer: %1% | %2 | %3")
304  .arg(notification->getDataObject())
305  .arg(notification->getObjectField())
306  .arg(notification->toString());
307 
308  _pendingNotifications.removeOne(notification);
309  }
310 }
311 
312 void SoundNotifyPlugin::stateChanged(QMediaPlayer::State newstate)
313 {
314 // qNotifyDebug() << "File length (ms): " << phonon.mo->totalTime();
315 
316 #ifndef Q_OS_WIN
317 // This is a hack to force Linux to wait until the end of the
318 // wav file before moving to the next in the queue.
319 // I wish I did not have to go through a #define, but I did not
320 // manage to make this work on both platforms any other way!
321 #endif
322  if ((newstate == QMediaPlayer::PausedState) || (newstate == QMediaPlayer::StoppedState)) {
323  qNotifyDebug() << "New State: " << QVariant(newstate).toString();
324 
325  // assignment to NULL needed to detect that palying is finished
326  // it's useful in repeat timer handler, where we can detect
327  // that notification has not overlap with itself
328  _nowPlayingNotification = NULL;
329 
330  if (!_pendingNotifications.isEmpty()) {
331  NotificationItem *notification = _pendingNotifications.takeFirst();
332  qNotifyDebug_if(notification) << "play audioFree - " << notification->toString();
333  playNotification(notification);
334  qNotifyDebug() << "end playNotification";
335  }
336  }
337 }
338 
339 bool checkRange(QString fieldValue, QString enumValue, QStringList /* values */, int direction)
340 {
341 
342  bool ret = false;
343  switch (direction) {
345  ret = !QString::compare(enumValue, fieldValue, Qt::CaseInsensitive) ? true : false;
346  break;
347 
348  default:
349  ret = true;
350  break;
351  };
352  return ret;
353 }
354 
355 bool checkRange(double fieldValue, double min, double max, int direction)
356 {
357  bool ret = false;
358  // Q_ASSERT(min < max);
359  switch (direction) {
361  ret = (fieldValue == min);
362  break;
363 
365  ret = (fieldValue > min);
366  break;
367 
369  ret = (fieldValue < min);
370  break;
371 
372  default:
373  ret = (fieldValue > min) && (fieldValue < max);
374  break;
375  };
376 
377  return ret;
378 }
379 
380 void SoundNotifyPlugin::checkNotificationRule(NotificationItem *notification, UAVObject *object)
381 {
382  if (notification->getDataObject() != object->getName()
383  || object->getField(notification->getObjectField()) == NULL)
384  return;
385  bool condition = false;
386 
387  if (notification->mute())
388  return;
389 
390  int direction = notification->getCondition();
391  QString fieldName = notification->getObjectField();
392  UAVObjectField *field = object->getField(fieldName);
393 
394  if (field->getName().isEmpty())
395  return;
396 
397  QVariant value = field->getValue();
398  if (UAVObjectField::ENUM == field->getType()) {
399  qNotifyDebug() << "Check range ENUM" << value.toString() << "|"
400  << notification->singleValue().toString() << "|" << field->getOptions()
401  << "|" << direction
402  << checkRange(value.toString(), notification->singleValue().toString(),
403  field->getOptions(), direction);
404  ;
405  condition = checkRange(value.toString(), notification->singleValue().toString(),
406  field->getOptions(), direction);
407  } else {
408  qNotifyDebug() << "Check range VAL" << value.toString() << "|"
409  << notification->singleValue().toString() << "|" << field->getOptions()
410  << "|" << direction
411  << checkRange(value.toDouble(), notification->singleValue().toDouble(),
412  notification->valueRange2(), direction);
413  condition = checkRange(value.toDouble(), notification->singleValue().toDouble(),
414  notification->valueRange2(), direction);
415  }
416 
417  notification->_isPlayed = condition;
418  // if condition has been changed, and already in false state
419  // we should reset _isPlayed flag and stop repeat timer
420  if (!notification->_isPlayed) {
421  notification->stopTimer();
422  notification->setCurrentUpdatePlayed(false);
423  return;
424  }
426  && notification->getCurrentUpdatePlayed())
427  return;
428 
429  if (!playNotification(notification)) {
430  if (!_pendingNotifications.contains(notification)
431  && (_nowPlayingNotification != notification)) {
432  notification->stopTimer();
433 
434  qNotifyDebug() << "add to pending list - " << notification->toString();
435  // if audio is busy, start expiration timer
436  // ms = (notification->getExpiredTimeout()[in sec])*1000
437  // QxtTimer::singleShot(notification->getExpireTimeout()*1000, this,
438  // SLOT(expirationTimerHandler(NotificationItem*)), qVariantFromValue(notification));
439  _pendingNotifications.append(notification);
440  notification->startExpireTimer();
441  connect(notification->getExpireTimer(), &QTimer::timeout, this,
442  &SoundNotifyPlugin::on_expiredTimer_Notification, Qt::UniqueConnection);
443  }
444  }
445 }
446 
447 bool SoundNotifyPlugin::playNotification(NotificationItem *notification)
448 {
449  playlist = new QMediaPlaylist;
450  if (!notification)
451  return false;
452 
453  // Check: race condition, if phonon.mo got deleted don't go further
454  if (phonon.mo == NULL)
455  return false;
456 
457  // qNotifyDebug() << "Phonon State: " << phonon.mo->state();
458 
459  if ((phonon.mo->state() == QMediaPlayer::PausedState)
460  || (phonon.mo->state() == QMediaPlayer::StoppedState) || phonon.firstPlay) {
461  _nowPlayingNotification = notification;
462  notification->stopExpireTimer();
463 
464  if (notification->retryValue() == NotificationItem::repeatOnce) {
465  _toRemoveNotifications.append(
466  _notificationList.takeAt(_notificationList.indexOf(notification)));
467  } else if (notification->retryValue() == NotificationItem::repeatOncePerUpdate)
468  notification->setCurrentUpdatePlayed(true);
469  else {
470  if (notification->retryValue() != NotificationItem::repeatInstantly) {
471  QRegExp rxlen("(\\d+)");
472  QString value;
473  int timer_value = 0;
474  int pos =
475  rxlen.indexIn(NotificationItem::retryValues.at(notification->retryValue()));
476  if (pos > -1) {
477  value = rxlen.cap(1); // "189"
478 
479  // needs to correct repeat timer value,
480  // acording to message play duration,
481  // we don't measure duration of each message,
482  // simply take average duration
483  enum { eAverageDurationSec = 8 };
484 
485  enum { eSecToMsec = 1000 };
486 
487  timer_value = (value.toInt() + eAverageDurationSec) * eSecToMsec;
488  }
489 
490  notification->startTimer(timer_value);
491  connect(notification->getTimer(), &QTimer::timeout, this,
492  &SoundNotifyPlugin::on_timerRepeated_Notification, Qt::UniqueConnection);
493  }
494  }
495  phonon.mo->stop();
496  qNotifyDebug() << "play: " << notification->toString();
497  foreach (QString item, notification->toSoundList()) {
498  playlist->addMedia(QUrl::fromLocalFile(item));
499  }
500  qNotifyDebug() << "begin play";
501  phonon.mo->setPlaylist(playlist);
502  phonon.mo->play();
503  qNotifyDebug() << "end play";
504  phonon.firstPlay =
505  false; // On Linux, you sometimes have to nudge Phonon to play 1 time before
506  // the state is not "Loading" anymore.
507  return true;
508  }
509 
510  return false;
511 }
QVariant singleValue() const
Uses to logging only inside notify plugin, can be convinient turned on/off.
QMediaPlayer * mo
Definition: notifyplugin.h:47
QStringList & toSoundList()
QString getObjectField() const
Core plugin system that manages the plugins, their life cycle and their registered objects...
Definition: pluginmanager.h:53
QVariant getValue(int index=0) const
Notify Plugin options page header.
virtual void readSettings(IConfigurablePlugin *plugin, QSettings *qs=nullptr)=0
int getCondition() const
for i
Definition: OPPlots.m:140
void objectUpdated(UAVObject *obj)
Signal sent whenever any field of the object is updated.
void saveState(QSettings *settings) const
void setCurrentUpdatePlayed(bool value)
bool mute() const
QDebug qNotifyDebug()
QStringList getOptions() const
bool checkRange(QString fieldValue, QString enumValue, QStringList, int direction)
QTimer * getExpireTimer() const
void restoreState(QSettings *settings)
static ICore * instance()
Definition: coreimpl.cpp:46
void updateNotificationList(QList< NotificationItem * > list)
static QStringList retryValues
void startTimer(int value)
double valueRange2() const
QString getName() const
void objectAdded(QObject *obj)
QTimer * getTimer() const
QString getDataObject() const
QString getName()
Definition: uavobject.cpp:131
int retryValue() const
virtual void saveSettings(IConfigurablePlugin *plugin, QSettings *qs=nullptr)=0
void addAutoReleasedObject(QObject *obj)
Definition: iplugin.cpp:306
UAVObject * getObject(const QString &name, quint32 instId=0)
FieldType getType() const