mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-12-14 21:23:52 +01:00
541 lines
18 KiB
C++
541 lines
18 KiB
C++
/**
|
|
******************************************************************************
|
|
*
|
|
* @file notifyplugin.cpp
|
|
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
|
* @brief
|
|
* @see The GNU Public License (GPL) Version 3
|
|
* @defgroup notifyplugin
|
|
* @{
|
|
*
|
|
*****************************************************************************/
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "notifyplugin.h"
|
|
#include "notificationitem.h"
|
|
#include "notifypluginoptionspage.h"
|
|
#include "notifylogging.h"
|
|
#include <coreplugin/icore.h>
|
|
#include <QDebug>
|
|
#include <QtPlugin>
|
|
#include <QStringList>
|
|
|
|
#include <extensionsystem/pluginmanager.h>
|
|
|
|
#include <iostream>
|
|
|
|
static const QString VERSION = "1.0.0";
|
|
|
|
// #define DEBUG_NOTIFIES
|
|
|
|
|
|
SoundNotifyPlugin::SoundNotifyPlugin()
|
|
{
|
|
phonon.mo = NULL;
|
|
}
|
|
|
|
SoundNotifyPlugin::~SoundNotifyPlugin()
|
|
{
|
|
Core::ICore::instance()->saveSettings(this);
|
|
|
|
if (phonon.mo != NULL) {
|
|
delete phonon.mo;
|
|
}
|
|
}
|
|
|
|
bool SoundNotifyPlugin::initialize(const QStringList & args, QString *errMsg)
|
|
{
|
|
Q_UNUSED(args);
|
|
Q_UNUSED(errMsg);
|
|
|
|
mop = new NotifyPluginOptionsPage(this);
|
|
addAutoReleasedObject(mop);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SoundNotifyPlugin::extensionsInitialized()
|
|
{
|
|
Core::ICore::instance()->readSettings(this);
|
|
|
|
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
|
|
|
connect(pm, SIGNAL(objectAdded(QObject *)), this, SLOT(onTelemetryManagerAdded(QObject *)));
|
|
_toRemoveNotifications.clear();
|
|
connectNotifications();
|
|
}
|
|
|
|
void SoundNotifyPlugin::saveConfig(QSettings *settings, UAVConfigInfo *configInfo)
|
|
{
|
|
configInfo->setVersion(VERSION);
|
|
|
|
settings->beginWriteArray("Current");
|
|
settings->setArrayIndex(0);
|
|
currentNotification.saveState(settings);
|
|
settings->endArray();
|
|
|
|
settings->beginGroup("listNotifies");
|
|
settings->remove("");
|
|
settings->endGroup();
|
|
|
|
settings->beginWriteArray("listNotifies");
|
|
for (int i = 0; i < _notificationList.size(); i++) {
|
|
settings->setArrayIndex(i);
|
|
_notificationList.at(i)->saveState(settings);
|
|
}
|
|
settings->endArray();
|
|
settings->setValue(QLatin1String("EnableSound"), enableSound);
|
|
}
|
|
|
|
void SoundNotifyPlugin::readConfig(QSettings *settings, UAVConfigInfo * /* configInfo */)
|
|
{
|
|
// Just for migration to the new format.
|
|
// Q_ASSERT(configInfo->version() == UAVConfigVersion());
|
|
|
|
settings->beginReadArray("Current");
|
|
settings->setArrayIndex(0);
|
|
currentNotification.restoreState(settings);
|
|
settings->endArray();
|
|
|
|
// read list of notifications from settings
|
|
int size = settings->beginReadArray("listNotifies");
|
|
for (int i = 0; i < size; ++i) {
|
|
settings->setArrayIndex(i);
|
|
NotificationItem *notification = new NotificationItem;
|
|
notification->restoreState(settings);
|
|
_notificationList.append(notification);
|
|
}
|
|
settings->endArray();
|
|
setEnableSound(settings->value(QLatin1String("EnableSound"), 0).toBool());
|
|
}
|
|
|
|
void SoundNotifyPlugin::onTelemetryManagerAdded(QObject *obj)
|
|
{
|
|
telMngr = qobject_cast<TelemetryManager *>(obj);
|
|
if (telMngr) {
|
|
connect(telMngr, SIGNAL(disconnected()), this, SLOT(onAutopilotDisconnect()));
|
|
}
|
|
}
|
|
|
|
void SoundNotifyPlugin::shutdown()
|
|
{
|
|
// Do nothing
|
|
}
|
|
|
|
void SoundNotifyPlugin::onAutopilotDisconnect()
|
|
{
|
|
connectNotifications();
|
|
}
|
|
|
|
/*!
|
|
clear any notify timers from previous flight;
|
|
reset will be perform on start of option page
|
|
*/
|
|
void SoundNotifyPlugin::resetNotification(void)
|
|
{
|
|
// first, reject empty args and unknown fields.
|
|
foreach(NotificationItem * ntf, _notificationList) {
|
|
ntf->disposeTimer();
|
|
disconnect(ntf->getTimer(), SIGNAL(timeout()), this, SLOT(on_timerRepeated_Notification()));
|
|
ntf->disposeExpireTimer();
|
|
disconnect(ntf->getExpireTimer(), SIGNAL(timeout()), this, SLOT(on_timerRepeated_Notification()));
|
|
}
|
|
}
|
|
|
|
/*!
|
|
update list of notifies;
|
|
will be perform on OK or APPLY press of option page
|
|
*/
|
|
void SoundNotifyPlugin::updateNotificationList(QList<NotificationItem *> list)
|
|
{
|
|
_toRemoveNotifications.clear();
|
|
resetNotification();
|
|
_notificationList.clear();
|
|
_notificationList = list;
|
|
connectNotifications();
|
|
|
|
Core::ICore::instance()->saveSettings(this);
|
|
}
|
|
|
|
void SoundNotifyPlugin::connectNotifications()
|
|
{
|
|
foreach(UAVDataObject * obj, lstNotifiedUAVObjects) {
|
|
if (obj != NULL) {
|
|
disconnect(obj, SIGNAL(objectUpdated(UAVObject *)), this, SLOT(on_arrived_Notification(UAVObject *)));
|
|
}
|
|
}
|
|
if (phonon.mo != NULL) {
|
|
delete phonon.mo;
|
|
phonon.mo = NULL;
|
|
}
|
|
|
|
if (!enableSound) {
|
|
return;
|
|
}
|
|
|
|
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
|
UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
|
|
|
|
lstNotifiedUAVObjects.clear();
|
|
_pendingNotifications.clear();
|
|
_notificationList.append(_toRemoveNotifications);
|
|
_toRemoveNotifications.clear();
|
|
|
|
// first, reject empty args and unknown fields.
|
|
foreach(NotificationItem * notify, _notificationList) {
|
|
notify->_isPlayed = false;
|
|
notify->isNowPlaying = false;
|
|
|
|
if (notify->mute()) {
|
|
continue;
|
|
}
|
|
// check is all sounds presented for notification,
|
|
// if not - we must not subscribe to it at all
|
|
if (notify->toSoundList().isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
UAVDataObject *obj = dynamic_cast<UAVDataObject *>(objManager->getObject(notify->getDataObject()));
|
|
if (obj != NULL) {
|
|
if (!lstNotifiedUAVObjects.contains(obj)) {
|
|
lstNotifiedUAVObjects.append(obj);
|
|
|
|
connect(obj, SIGNAL(objectUpdated(UAVObject *)),
|
|
this, SLOT(on_arrived_Notification(UAVObject *)),
|
|
Qt::QueuedConnection);
|
|
}
|
|
} else {
|
|
qNotifyDebug() << "Error: Object is unknown (" << notify->getDataObject() << ").";
|
|
}
|
|
}
|
|
|
|
if (_notificationList.isEmpty()) {
|
|
return;
|
|
}
|
|
// set notification message to current event
|
|
phonon.mo = new QMediaPlayer;
|
|
phonon.firstPlay = true;
|
|
connect(phonon.mo, SIGNAL(stateChanged(QMediaPlayer::State)),
|
|
this, SLOT(stateChanged(QMediaPlayer::State)));
|
|
}
|
|
|
|
void SoundNotifyPlugin::on_arrived_Notification(UAVObject *object)
|
|
{
|
|
foreach(NotificationItem * ntf, _notificationList) {
|
|
if (object->getName() != ntf->getDataObject()) {
|
|
continue;
|
|
}
|
|
|
|
|
|
// skip duplicate notifications
|
|
if (_nowPlayingNotification == ntf) {
|
|
continue;
|
|
}
|
|
|
|
// skip periodical notifications
|
|
// this condition accepts:
|
|
// 1. Periodical notifications played firstly;
|
|
// NOTE: At first time it will be played, then it played only by timer,
|
|
// when conditions became false firstStart flag has been cleared and
|
|
// notification can be accepted again;
|
|
// 2. Once time notifications, they removed immediately after first playing;
|
|
// 3. Instant notifications(played one by one without interval);
|
|
if (ntf->retryValue() != NotificationItem::repeatInstantly && ntf->retryValue() != NotificationItem::repeatOncePerUpdate &&
|
|
ntf->retryValue() != NotificationItem::repeatOnce && ntf->_isPlayed) {
|
|
continue;
|
|
}
|
|
|
|
qNotifyDebug() << QString("new notification: | %1 | %2 | val1: %3 | val2: %4")
|
|
.arg(ntf->getDataObject())
|
|
.arg(ntf->getObjectField())
|
|
.arg(ntf->singleValue().toString())
|
|
.arg(ntf->valueRange2());
|
|
|
|
checkNotificationRule(ntf, object);
|
|
}
|
|
connect(object, SIGNAL(objectUpdated(UAVObject *)),
|
|
this, SLOT(on_arrived_Notification(UAVObject *)), Qt::UniqueConnection);
|
|
}
|
|
|
|
|
|
void SoundNotifyPlugin::on_timerRepeated_Notification()
|
|
{
|
|
NotificationItem *notification = static_cast<NotificationItem *>(sender()->parent());
|
|
|
|
if (!notification) {
|
|
return;
|
|
}
|
|
// skip duplicate notifications
|
|
// WARNING: generally we shoudn't ever trap here
|
|
// this means, that timer fires to early and notification overlap itself
|
|
if (_nowPlayingNotification == notification) {
|
|
qNotifyDebug() << "WARN: on_timerRepeated - notification was skipped!";
|
|
notification->restartTimer();
|
|
return;
|
|
}
|
|
|
|
qNotifyDebug() << QString("repeatTimer: %1% | %2 | %3").arg(notification->getDataObject())
|
|
.arg(notification->getObjectField())
|
|
.arg(notification->toString());
|
|
|
|
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
|
UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
|
|
UAVObject *object = objManager->getObject(notification->getDataObject());
|
|
if (object) {
|
|
checkNotificationRule(notification, object);
|
|
}
|
|
}
|
|
|
|
|
|
void SoundNotifyPlugin::on_expiredTimer_Notification()
|
|
{
|
|
// fire expire timer
|
|
NotificationItem *notification = static_cast<NotificationItem *>(sender()->parent());
|
|
|
|
if (!notification) {
|
|
return;
|
|
}
|
|
notification->stopExpireTimer();
|
|
|
|
if (!_pendingNotifications.isEmpty()) {
|
|
qNotifyDebug() << QString("expireTimer: %1% | %2 | %3").arg(notification->getDataObject())
|
|
.arg(notification->getObjectField())
|
|
.arg(notification->toString());
|
|
|
|
_pendingNotifications.removeOne(notification);
|
|
}
|
|
}
|
|
|
|
void SoundNotifyPlugin::stateChanged(QMediaPlayer::State newstate)
|
|
{
|
|
// qNotifyDebug() << "File length (ms): " << phonon.mo->totalTime();
|
|
|
|
#ifndef Q_OS_WIN
|
|
// This is a hack to force Linux to wait until the end of the
|
|
// wav file before moving to the next in the queue.
|
|
// I wish I did not have to go through a #define, but I did not
|
|
// manage to make this work on both platforms any other way!
|
|
// if (phonon.mo->totalTime() > 0) {
|
|
// phonon.mo->setTransitionTime(phonon.mo->totalTime());
|
|
// }
|
|
#endif
|
|
if ((newstate == QMediaPlayer::PausedState) ||
|
|
(newstate == QMediaPlayer::StoppedState)) {
|
|
qNotifyDebug() << "New State: " << QVariant(newstate).toString();
|
|
|
|
// assignment to NULL needed to detect that palying is finished
|
|
// it's useful in repeat timer handler, where we can detect
|
|
// that notification has not overlap with itself
|
|
_nowPlayingNotification = NULL;
|
|
|
|
if (!_pendingNotifications.isEmpty()) {
|
|
NotificationItem *notification = _pendingNotifications.takeFirst();
|
|
qNotifyDebug_if(notification) << "play audioFree - " << notification->toString();
|
|
playNotification(notification);
|
|
qNotifyDebug() << "end playNotification";
|
|
}
|
|
} else {
|
|
if (newstate == QMediaPlayer::ServiceMissingError) {
|
|
if (phonon.mo->error() == 0) {
|
|
qDebug() << "Phonon::ErrorState: ErrorType = " << phonon.mo->error();
|
|
phonon.mo->stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool checkRange(QString fieldValue, QString enumValue, QStringList /* values */, int direction)
|
|
{
|
|
bool ret = false;
|
|
|
|
switch (direction) {
|
|
case NotifyPluginOptionsPage::equal:
|
|
ret = !QString::compare(enumValue, fieldValue, Qt::CaseInsensitive) ? true : false;
|
|
break;
|
|
|
|
default:
|
|
ret = true;
|
|
break;
|
|
}
|
|
;
|
|
return ret;
|
|
}
|
|
|
|
bool checkRange(double fieldValue, double min, double max, int direction)
|
|
{
|
|
bool ret = false;
|
|
|
|
// Q_ASSERT(min < max);
|
|
switch (direction) {
|
|
case NotifyPluginOptionsPage::equal:
|
|
ret = (fieldValue == min);
|
|
break;
|
|
|
|
case NotifyPluginOptionsPage::bigger:
|
|
ret = (fieldValue > min);
|
|
break;
|
|
|
|
case NotifyPluginOptionsPage::smaller:
|
|
ret = (fieldValue < min);
|
|
break;
|
|
|
|
default:
|
|
ret = (fieldValue > min) && (fieldValue < max);
|
|
break;
|
|
}
|
|
;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SoundNotifyPlugin::checkNotificationRule(NotificationItem *notification, UAVObject *object)
|
|
{
|
|
if (notification->getDataObject() != object->getName() || object->getField(notification->getObjectField()) == NULL) {
|
|
return;
|
|
}
|
|
bool condition = false;
|
|
|
|
if (notification->mute()) {
|
|
return;
|
|
}
|
|
|
|
int direction = notification->getCondition();
|
|
QString fieldName = notification->getObjectField();
|
|
UAVObjectField *field = object->getField(fieldName);
|
|
|
|
if (field->getName().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QVariant value = field->getValue();
|
|
if (UAVObjectField::ENUM == field->getType()) {
|
|
qNotifyDebug() << "Check range ENUM" << value.toString() << "|" << notification->singleValue().toString() << "|" << field->getOptions() << "|" <<
|
|
direction << checkRange(value.toString(),
|
|
notification->singleValue().toString(),
|
|
field->getOptions(),
|
|
direction);;
|
|
condition = checkRange(value.toString(),
|
|
notification->singleValue().toString(),
|
|
field->getOptions(),
|
|
direction);
|
|
} else {
|
|
qNotifyDebug() << "Check range VAL" << value.toString() << "|" << notification->singleValue().toString() << "|" << field->getOptions() << "|" <<
|
|
direction << checkRange(value.toDouble(),
|
|
notification->singleValue().toDouble(),
|
|
notification->valueRange2(),
|
|
direction);
|
|
condition = checkRange(value.toDouble(),
|
|
notification->singleValue().toDouble(),
|
|
notification->valueRange2(),
|
|
direction);
|
|
}
|
|
|
|
notification->_isPlayed = condition;
|
|
// if condition has been changed, and already in false state
|
|
// we should reset _isPlayed flag and stop repeat timer
|
|
if (!notification->_isPlayed) {
|
|
notification->stopTimer();
|
|
notification->setCurrentUpdatePlayed(false);
|
|
return;
|
|
}
|
|
if (notification->retryValue() == NotificationItem::repeatOncePerUpdate && notification->getCurrentUpdatePlayed()) {
|
|
return;
|
|
}
|
|
|
|
if (!playNotification(notification)) {
|
|
if (!_pendingNotifications.contains(notification)
|
|
&& (_nowPlayingNotification != notification)) {
|
|
notification->stopTimer();
|
|
|
|
qNotifyDebug() << "add to pending list - " << notification->toString();
|
|
// if audio is busy, start expiration timer
|
|
// ms = (notification->getExpiredTimeout()[in sec])*1000
|
|
// QxtTimer::singleShot(notification->getExpireTimeout()*1000, this, SLOT(expirationTimerHandler(NotificationItem*)), qVariantFromValue(notification));
|
|
_pendingNotifications.append(notification);
|
|
notification->startExpireTimer();
|
|
connect(notification->getExpireTimer(), SIGNAL(timeout()),
|
|
this, SLOT(on_expiredTimer_Notification()), Qt::UniqueConnection);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SoundNotifyPlugin::playNotification(NotificationItem *notification)
|
|
{
|
|
playlist = new QMediaPlaylist;
|
|
if (!notification) {
|
|
return false;
|
|
}
|
|
|
|
// Check: race condition, if phonon.mo got deleted don't go further
|
|
if (phonon.mo == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// qNotifyDebug() << "Phonon State: " << phonon.mo->state();
|
|
|
|
if ((phonon.mo->state() == QMediaPlayer::PausedState)
|
|
|| (phonon.mo->state() == QMediaPlayer::StoppedState)
|
|
|| phonon.firstPlay) {
|
|
_nowPlayingNotification = notification;
|
|
notification->stopExpireTimer();
|
|
|
|
if (notification->retryValue() == NotificationItem::repeatOnce) {
|
|
_toRemoveNotifications.append(_notificationList.takeAt(_notificationList.indexOf(notification)));
|
|
} else if (notification->retryValue() == NotificationItem::repeatOncePerUpdate) {
|
|
notification->setCurrentUpdatePlayed(true);
|
|
} else {
|
|
if (notification->retryValue() != NotificationItem::repeatInstantly) {
|
|
QRegExp rxlen("(\\d+)");
|
|
QString value;
|
|
int timer_value = 0;
|
|
int pos = rxlen.indexIn(NotificationItem::retryValues.at(notification->retryValue()));
|
|
if (pos > -1) {
|
|
value = rxlen.cap(1); // "189"
|
|
|
|
// needs to correct repeat timer value,
|
|
// acording to message play duration,
|
|
// we don't measure duration of each message,
|
|
// simply take average duration
|
|
enum { eAverageDurationSec = 8 };
|
|
|
|
enum { eSecToMsec = 1000 };
|
|
|
|
timer_value = (value.toInt() + eAverageDurationSec) * eSecToMsec;
|
|
}
|
|
|
|
notification->startTimer(timer_value);
|
|
connect(notification->getTimer(), SIGNAL(timeout()),
|
|
this, SLOT(on_timerRepeated_Notification()), Qt::UniqueConnection);
|
|
}
|
|
}
|
|
phonon.mo->stop();
|
|
qNotifyDebug() << "play: " << notification->toString();
|
|
foreach(QString item, notification->toSoundList()) {
|
|
playlist->addMedia(QUrl::fromLocalFile(item));
|
|
}
|
|
qNotifyDebug() << "begin play";
|
|
phonon.mo->setPlaylist(playlist);
|
|
phonon.mo->play();
|
|
qNotifyDebug() << "end play";
|
|
phonon.firstPlay = false; // On Linux, you sometimes have to nudge Phonon to play 1 time before
|
|
// the state is not "Loading" anymore.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|