File indexing completed on 2024-11-10 05:11:10

0001 /*
0002  * Copyright 2018 by Marco Martin <mart@kde.org>
0003  * Copyright 2018 David Edmundson <davidedmundson@kde.org>
0004  *
0005  * Licensed under the Apache License, Version 2.0 (the "License");
0006  * you may not use this file except in compliance with the License.
0007  * You may obtain a copy of the License at
0008  *
0009  *    http://www.apache.org/licenses/LICENSE-2.0
0010  *
0011  * Unless required by applicable law or agreed to in writing, software
0012  * distributed under the License is distributed on an "AS IS" BASIS,
0013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014  * See the License for the specific language governing permissions and
0015  * limitations under the License.
0016  *
0017  */
0018 
0019 #include "mycroftcontroller.h"
0020 #include "globalsettings.h"
0021 #include "abstractdelegate.h"
0022 #include "activeskillsmodel.h"
0023 #include "abstractskillview.h"
0024 #include "controllerconfig.h"
0025 
0026 #include <QtGlobal>
0027 #include <QFile>
0028 #include <QJsonObject>
0029 #include <QJsonArray>
0030 #include <QJsonDocument>
0031 #include <QDebug>
0032 #include <QProcess>
0033 #include <QQmlPropertyMap>
0034 #include <QStandardItemModel>
0035 #include <QQmlEngine>
0036 #include <QQmlContext>
0037 #include <QUuid>
0038 #include <QWebSocket>
0039 #include <QMediaPlayer>
0040 
0041 MycroftController *MycroftController::instance()
0042 {
0043     static MycroftController* s_self = nullptr;
0044     if (!s_self) {
0045         s_self = new MycroftController;
0046     }
0047     return s_self;
0048 }
0049 
0050 
0051 MycroftController::MycroftController(QObject *parent)
0052     : QObject(parent),
0053       m_appSettingObj(new GlobalSettings)
0054 {
0055     m_qt_version_context = QStringLiteral("6");
0056     connect(&m_mainWebSocket, &QWebSocket::connected, this,
0057             [this] () {
0058                 m_reconnectTimer.stop();
0059                 emit socketStatusChanged();
0060             });
0061     connect(&m_mainWebSocket, &QWebSocket::disconnected, this, &MycroftController::closed);
0062     connect(&m_mainWebSocket, &QWebSocket::stateChanged, this,
0063             [this] (QAbstractSocket::SocketState state) {
0064                 emit socketStatusChanged();
0065                 if (state == QAbstractSocket::ConnectedState) {
0066                     qWarning() << "Main Socket connected, trying to connect gui";
0067                     #if QT_VERSION >= 0x060000
0068                         m_qt_version_context = QStringLiteral("6");
0069                     #else
0070                         m_qt_version_context = QStringLiteral("5");
0071                     #endif
0072                     for (const auto &guiId : m_views.keys()) {
0073                         sendRequest(QStringLiteral("mycroft.gui.connected"),
0074                                     QVariantMap({{QStringLiteral("gui_id"), guiId}}),
0075                                     QVariantMap({{QStringLiteral("qt_version"), m_qt_version_context}}));
0076                     }
0077                     m_reannounceGuiTimer.start();
0078 
0079                     sendRequest(QStringLiteral("mycroft.skills.all_loaded"), QVariantMap());
0080                 } else {
0081                     if (m_serverReady) {
0082                         m_serverReady = false;
0083                         emit serverReadyChanged();
0084                     }
0085                 }
0086             });
0087 
0088     connect(&m_mainWebSocket, &QWebSocket::textMessageReceived, this, &MycroftController::onMainSocketMessageReceived);
0089 
0090     m_reconnectTimer.setInterval(1000);
0091     connect(&m_reconnectTimer, &QTimer::timeout, this, [this]() {
0092         QString socket = m_appSettingObj->webSocketAddress() + QStringLiteral(":8181/core");
0093         m_mainWebSocket.open(QUrl(socket));
0094     });
0095 
0096     m_reannounceGuiTimer.setInterval(10000);
0097     connect(&m_reannounceGuiTimer, &QTimer::timeout, this, [this]() {
0098         if (m_mainWebSocket.state() != QAbstractSocket::ConnectedState) {
0099             return;
0100         }
0101         for (const auto &guiId : m_views.keys()) {
0102             if (m_views[guiId]->status() != Open) {
0103                 qWarning()<<"Retrying to announce gui";
0104                 sendRequest(QStringLiteral("mycroft.gui.connected"),
0105                             QVariantMap({{QStringLiteral("gui_id"), guiId}}), 
0106                             QVariantMap({{QStringLiteral("qt_version"), m_qt_version_context}}));
0107             }
0108         }
0109     });
0110 
0111 #ifdef Q_OS_ANDROID
0112     m_speech = new QTextToSpeech(this);
0113     connect(m_speech, &QTextToSpeech::stateChanged, this, [this] () {
0114         if (!ttsqueue.isEmpty() && m_speech->state() != QTextToSpeech::Speaking) {
0115             m_speech->say(ttsqueue.dequeue());
0116         }
0117         
0118         if (ttsqueue.isEmpty() && m_speech->state() != QTextToSpeech::Speaking && m_isExpectingSpeechResponse) {
0119             emit speechRequestedChanged(m_isExpectingSpeechResponse);
0120             m_isExpectingSpeechResponse = false;
0121         }
0122     });
0123 #endif
0124 
0125 }
0126 
0127 
0128 void MycroftController::start()
0129 {
0130     //auto appSettingObj = new GlobalSettings;
0131     QString socket = m_appSettingObj->webSocketAddress() + QStringLiteral(":8181/core");
0132     m_mainWebSocket.open(QUrl(socket));
0133     connect(&m_mainWebSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
0134             this, [this] (const QAbstractSocket::SocketError &error) {
0135         //qDebug() << error;
0136 
0137         if (error != QAbstractSocket::HostNotFoundError && error != QAbstractSocket::ConnectionRefusedError) {
0138             qWarning() << "Mycroft is running but the connection failed for some reason. Kill Mycroft manually.";
0139 
0140             return;
0141         }
0142 
0143         //don't try to launch mycroft more than once
0144         if (!m_mycroftLaunched) {
0145             QProcess::startDetached(QStringLiteral("mycroft-gui-core-loader"), QStringList());
0146             m_mycroftLaunched = true;
0147             if(m_appSettingObj->usePTTClient()){
0148                 QProcess::startDetached(QStringLiteral("mycroft-gui-ptt-loader"), QStringList());
0149             }
0150         }
0151         m_reconnectTimer.start();
0152         emit socketStatusChanged();
0153     });
0154 
0155     emit socketStatusChanged();
0156 }
0157 
0158 void MycroftController::disconnectSocket()
0159 {
0160     qDebug() << "in reconnect";
0161     m_mainWebSocket.close();
0162     m_reconnectTimer.stop();
0163     if (m_mycroftLaunched) {
0164         QProcess::startDetached(QStringLiteral("mycroft-gui-core-stop"), QStringList());
0165         m_mycroftLaunched = false;
0166     }
0167     emit socketStatusChanged();
0168 }
0169 
0170 void MycroftController::reconnect()
0171 {
0172     qDebug() << "in reconnect";
0173     m_mainWebSocket.close();
0174     m_reconnectTimer.start();
0175     emit socketStatusChanged();
0176 }
0177 
0178 void MycroftController::startPTTClient()
0179 {
0180     QProcess::startDetached(QStringLiteral("mycroft-gui-ptt-loader"), QStringList());
0181 }
0182 
0183 void MycroftController::onMainSocketMessageReceived(const QString &message)
0184 {
0185     auto doc = QJsonDocument::fromJson(message.toUtf8());
0186 
0187     if (doc.isEmpty()) {
0188         qWarning() << "Empty or invalid JSON message arrived on the main socket:" << message;
0189         return;
0190     }
0191 
0192     auto type = doc[QStringLiteral("type")].toString();
0193 
0194     if (type.isEmpty()) {
0195         qWarning() << "Empty type in the JSON message on the main socket";
0196         return;
0197     }
0198 
0199     //filter out the noise so we can print debug stuff later without drowning in noise
0200     if (type.startsWith(QStringLiteral("enclosure")) || type.startsWith(QStringLiteral("mycroft-date"))) {
0201         return;
0202     }
0203 
0204 #ifdef DEBUG_MYCROFT_MESSAGEBUS
0205     qDebug() << "type" << type;
0206 #endif
0207 
0208     emit intentRecevied(type, doc[QStringLiteral("data")].toVariant().toMap());
0209 
0210 #ifdef Q_OS_ANDROID
0211     if (type == QLatin1String("speak") && m_speech->state() != QTextToSpeech::Speaking) {
0212         m_speech->say(doc[QStringLiteral("data")][QStringLiteral("utterance")].toString());
0213     } else if (type == QLatin1String("speak") && m_speech->state() == QTextToSpeech::Speaking) {
0214         ttsqueue.enqueue(doc[QStringLiteral("data")][QStringLiteral("utterance")].toString());
0215     }
0216     
0217     if (type == QLatin1String("mycroft.mic.listen")) {
0218         m_isExpectingSpeechResponse = true;
0219     }
0220 #endif
0221 
0222     if (type == QLatin1String("remote.tts.audio") && m_appSettingObj->usesRemoteTTS()) {
0223         QString aud = doc[QStringLiteral("data")][QStringLiteral("wave")].toString();
0224         auto innerdoc = QJsonDocument::fromJson(aud.toUtf8());
0225         QJsonValue qjv = innerdoc[QStringLiteral("py/b64")];
0226         QString aud_values = qjv.toString();
0227         QByteArray audioopt;
0228         audioopt.append(qUtf8Printable(aud_values));
0229         QByteArray aud_array = QByteArray::fromBase64(audioopt, QByteArray::Base64UrlEncoding);
0230         QByteArray ret_aud = QByteArray::fromBase64(aud_array);
0231         //error: variable has incomplete type 'QFile'
0232         // fix: #include <QFile>
0233  
0234         QFile file(QStringLiteral("/tmp/incoming.wav"));
0235         file.open(QIODevice::WriteOnly);
0236         file.write(ret_aud);
0237         file.close();
0238         QMediaPlayer *player = new QMediaPlayer;
0239         player->setSource(QUrl::fromLocalFile(QStringLiteral("/tmp/incoming.wav")));
0240         player->play();
0241     }
0242 
0243     // Try catching intent_failure from another method because of issue: https://github.com/MycroftAI/mycroft-core/issues/2490
0244     if (type == QLatin1String("active_skill_request")) {         
0245         QString skill_id = doc[QStringLiteral("data")][QStringLiteral("skill_id")].toString();
0246         if (skill_id == QStringLiteral("fallback-unknown.mycroftai")) {
0247             m_isListening = false;
0248             emit isListeningChanged();
0249             emit notUnderstood();
0250         }
0251         return;
0252     }
0253     // Instead of intent_failure which is handled by fallback skills, use complete_intent_failure where all skills failed to parse intent
0254     if (type == QLatin1String("complete_intent_failure")) {
0255         m_isListening = false;
0256         emit isListeningChanged();
0257         emit notUnderstood();
0258     }
0259     if (type == QLatin1String("recognizer_loop:audio_output_start")) {
0260         m_isSpeaking = true;
0261         emit isSpeakingChanged();
0262         return;
0263     }
0264     if (type == QLatin1String("recognizer_loop:audio_output_end")) {
0265         m_isSpeaking = false;
0266         emit isSpeakingChanged();
0267         return;
0268     }
0269     if (type == QLatin1String("recognizer_loop:wakeword")) {
0270         m_isListening = true;
0271         emit isListeningChanged();
0272         return;
0273     }
0274     if (type == QLatin1String("recognizer_loop:record_begin") && !m_isListening) {
0275         m_isListening = true;
0276         emit isListeningChanged();
0277         return;
0278     }
0279     if (type == QLatin1String("recognizer_loop:record_end")) {
0280         m_isListening = false;
0281         emit isListeningChanged();
0282         return;
0283     }
0284     if (type == QLatin1String("mycroft.speech.recognition.unknown")) {
0285         emit notUnderstood();
0286         return;
0287     }
0288 
0289     if (type == QLatin1String("mycroft.skill.handler.start")) {
0290         m_currentSkill = doc[QStringLiteral("data")][QStringLiteral("name")].toString();
0291         qDebug() << "Current intent:" << m_currentIntent;
0292         emit currentIntentChanged();
0293     } else if (type == QLatin1String("mycroft.skill.handler.complete")) {
0294         m_currentSkill = QString();
0295         emit currentSkillChanged();
0296     } else if (type == QLatin1String("speak")) {
0297         emit fallbackTextRecieved(m_currentSkill, doc[QStringLiteral("data")].toVariant().toMap());
0298     } else if (type == QLatin1String("mycroft.stop.handled") || type == QLatin1String("mycroft.stop")) {
0299         emit stopped();
0300 
0301     } else if (type == QLatin1String("mycroft.gui.port")) {
0302         const int port = doc[QStringLiteral("data")][QStringLiteral("port")].toInt();
0303         const QString guiId = doc[QStringLiteral("data")][QStringLiteral("gui_id")].toString();
0304         if (port < 0 || port > 65535) {
0305             qWarning() << "Invalid port from mycroft.gui.port";
0306             return;
0307         }
0308 
0309         qWarning() << "Received port" << port << "for gui" << guiId;
0310         if (!m_views.contains(guiId)) {
0311             qWarning() << "Unknown guiId from mycroft.gui.port";
0312             return;
0313         }
0314 
0315         QUrl url(QStringLiteral("%1:%2/gui").arg(m_appSettingObj->webSocketAddress()).arg(port));
0316         m_views[guiId]->setUrl(url);
0317         m_reannounceGuiTimer.stop();
0318     } else if (type == QLatin1String("mycroft.skills.all_loaded.response")) {
0319         if (doc[QStringLiteral("data")][QStringLiteral("status")].toBool() == true) {
0320             m_serverReady = true;
0321             emit serverReadyChanged();
0322         }
0323     } else if (type == QLatin1String("mycroft.ready")) {
0324         m_serverReady = true;
0325         emit serverReadyChanged();
0326     }
0327     
0328     if (type == QLatin1String("screen.close.idle.event")) {
0329         QString skill_idle_event_id = doc[QStringLiteral("data")][QStringLiteral("skill_idle_event_id")].toString();
0330         emit skillTimeoutReceived(skill_idle_event_id);
0331     }
0332 
0333     // Check if it's an utterance recognized as an intent
0334     if (type.contains(QLatin1Char(':')) && !doc[QStringLiteral("data")][QStringLiteral("utterance")].toString().isEmpty()) {
0335         const QString skill = type.split(QLatin1Char(':')).first();
0336         if (skill.contains(QLatin1Char('.'))) {
0337             m_currentSkill = skill;
0338             qDebug() << "Current skill:" << m_currentSkill;
0339             emit utteranceManagedBySkill(m_currentSkill);
0340             emit currentSkillChanged();
0341         }
0342     }
0343 }
0344 
0345 void MycroftController::sendRequest(const QString &type, const QVariantMap &data, const QVariantMap &context)
0346 {
0347     if (m_mainWebSocket.state() != QAbstractSocket::ConnectedState) {
0348         qWarning() << "mycroft connection not open!";
0349         return;
0350     }
0351     QJsonObject root;
0352 
0353     root[QStringLiteral("type")] = type;
0354     root[QStringLiteral("data")] = QJsonObject::fromVariantMap(data);
0355     root[QStringLiteral("context")] = QJsonObject::fromVariantMap(context);
0356 
0357     QJsonDocument doc(root);
0358     m_mainWebSocket.sendTextMessage(QString::fromUtf8(doc.toJson()));
0359 }
0360 
0361 void MycroftController::sendBinary(const QString &type, const QJsonObject &data, const QVariantMap &context)
0362 {
0363     if (m_mainWebSocket.state() != QAbstractSocket::ConnectedState) {
0364         qWarning() << "mycroft connection not open!";
0365         return;
0366     }
0367     QJsonObject socketObject;
0368     socketObject[QStringLiteral("type")] = type;
0369     socketObject[QStringLiteral("data")] = data;
0370 
0371     if(m_appSettingObj->useHivemindProtocol()){
0372         socketObject[QStringLiteral("context")] = QJsonObject::fromVariantMap(context);
0373     }
0374 
0375     QJsonDocument doc;
0376     doc.setObject(socketObject);
0377     QByteArray docbin = doc.toJson(QJsonDocument::Compact);
0378     m_mainWebSocket.sendBinaryMessage(docbin);
0379 }
0380 
0381 void MycroftController::sendText(const QString &message)
0382 {
0383     if(!m_appSettingObj->useHivemindProtocol()){
0384         sendRequest(QStringLiteral("recognizer_loop:utterance"), QVariantMap({{QStringLiteral("utterances"), QStringList({message})}}), QVariantMap({{QStringLiteral("source"), QStringLiteral("debug_cli")}, {QStringLiteral("destination"), QStringLiteral("skills")}, {QStringLiteral("qt_version"), m_qt_version_context}}));
0385     } else {
0386         sendRequest(QStringLiteral("recognizer_loop:utterance"), QVariantMap({{QStringLiteral("utterances"), QStringList({message})}}), QVariantMap({{QStringLiteral("source"), QStringLiteral("mycroft-gui")}, {QStringLiteral("destination"), QStringLiteral("skills")}, {QStringLiteral("qt_version"), m_qt_version_context}}));
0387     }
0388 }
0389 
0390 void MycroftController::registerView(AbstractSkillView *view)
0391 {
0392     Q_ASSERT(!view->id().isEmpty());
0393     Q_ASSERT(!m_views.contains(view->id()));
0394     m_views[view->id()] = view;
0395 //TODO: manage view destruction
0396     if (m_mainWebSocket.state() == QAbstractSocket::ConnectedState) {
0397         sendRequest(QStringLiteral("mycroft.gui.connected"),
0398                     QVariantMap({{QStringLiteral("gui_id"), view->id()}}), 
0399                     QVariantMap({{QStringLiteral("qt_version"), m_qt_version_context}}));
0400     }
0401 }
0402 
0403 MycroftController::Status MycroftController::status() const
0404 {
0405     if (m_reconnectTimer.isActive()) {
0406         return Connecting;
0407     }
0408 
0409     switch(m_mainWebSocket.state())
0410     {
0411     case QAbstractSocket::ConnectingState:
0412     case QAbstractSocket::BoundState:
0413     case QAbstractSocket::HostLookupState:
0414         return Connecting;
0415     case QAbstractSocket::UnconnectedState:
0416         return Closed;
0417     case QAbstractSocket::ConnectedState:
0418         return Open;
0419     case QAbstractSocket::ClosingState:
0420         return Closing;
0421     default:
0422         return Connecting;
0423     }
0424 }
0425 
0426 //FIXME: remove
0427 QString MycroftController::currentSkill() const
0428 {
0429     return m_currentSkill;
0430 }
0431 
0432 QString MycroftController::currentIntent() const
0433 {
0434     return m_currentIntent;
0435 }
0436 
0437 bool MycroftController::isSpeaking() const
0438 {
0439     return m_isSpeaking;
0440 }
0441 
0442 bool MycroftController::isListening() const
0443 {
0444     return m_isListening;
0445 }
0446 
0447 bool MycroftController::serverReady() const
0448 {
0449     return m_serverReady;
0450 }
0451 
0452 #include "moc_mycroftcontroller.cpp"