diff options
author | Loïc Collignon <loic.collignon@iot.bzh> | 2018-02-23 13:57:35 +0100 |
---|---|---|
committer | Loïc Collignon <loic.collignon@iot.bzh> | 2018-02-23 13:57:35 +0100 |
commit | d2f071d5d8c5c21c4f438bf5a822fbaddb2d97ce (patch) | |
tree | e69b8367c41b2a23c107d86c2467977d7cb39d57 | |
parent | 43dc156f29b89efd4055b8b412e888c0c3129e7b (diff) |
hack: control master volume using 4a with the homescreen slidersandbox/ctxnop/4avolume
Change-Id: If527d160f8eacbcfed3df2ab744485995dbe1dc9
Signed-off-by: Loïc Collignon <loic.collignon@iot.bzh>
-rw-r--r-- | homescreen/homescreen.pro | 10 | ||||
-rw-r--r-- | homescreen/qml/MediaAreaBlank.qml | 6 | ||||
-rw-r--r-- | homescreen/src/main.cpp | 3 | ||||
-rw-r--r-- | homescreen/src/mastervolume.cpp | 127 | ||||
-rw-r--r-- | homescreen/src/mastervolume.h | 34 | ||||
-rw-r--r-- | homescreen/src/qafbwsclient.cpp | 136 | ||||
-rw-r--r-- | homescreen/src/qafbwsclient.h | 50 | ||||
-rw-r--r-- | homescreen/src/qafbwsmsg.cpp | 44 | ||||
-rw-r--r-- | homescreen/src/qafbwsmsg.h | 33 | ||||
-rw-r--r-- | package/config.xml | 3 |
10 files changed, 423 insertions, 23 deletions
diff --git a/homescreen/homescreen.pro b/homescreen/homescreen.pro index b72931b..2d20a03 100644 --- a/homescreen/homescreen.pro +++ b/homescreen/homescreen.pro @@ -15,7 +15,7 @@ TEMPLATE = app TARGET = HomeScreen -QT = qml quick dbus +QT = qml quick dbus core websockets CONFIG += c++11 link_pkgconfig DESTDIR = $${OUT_PWD}/../package/root/bin PKGCONFIG += qlibwindowmanager @@ -32,7 +32,9 @@ SOURCES += \ src/statusbarserver.cpp \ src/applicationlauncher.cpp \ src/mastervolume.cpp \ - src/homescreenhandler.cpp + src/homescreenhandler.cpp \ + src/qafbwsclient.cpp \ + src/qafbwsmsg.cpp HEADERS += \ src/statusbarmodel.h \ @@ -41,7 +43,9 @@ HEADERS += \ src/applicationmodel.h \ src/appinfo.h \ src/mastervolume.h \ - src/homescreenhandler.h + src/homescreenhandler.h \ + src/qafbwsclient.h \ + src/qafbwsmsg.h OTHER_FILES += \ README.md diff --git a/homescreen/qml/MediaAreaBlank.qml b/homescreen/qml/MediaAreaBlank.qml index 2b888c8..6114b55 100644 --- a/homescreen/qml/MediaAreaBlank.qml +++ b/homescreen/qml/MediaAreaBlank.qml @@ -71,6 +71,7 @@ Image { MasterVolume { id: mv objectName: "mv" + Component.onCompleted: mv.init(afbPort, afbToken) onVolumeChanged: slider.value = volume } @@ -101,10 +102,11 @@ Image { id: slider Layout.fillWidth: true from: 0 - to: 65536 - stepSize: 256 + to: 100 + stepSize: 1 snapMode: Slider.SnapOnRelease onValueChanged: mv.volume = value + value: 0 Component.onCompleted: value = mv.volume onPressedChanged: { if (pressed) {volume_timer.stop()} diff --git a/homescreen/src/main.cpp b/homescreen/src/main.cpp index a7f435a..283c513 100644 --- a/homescreen/src/main.cpp +++ b/homescreen/src/main.cpp @@ -85,6 +85,7 @@ int main(int argc, char *argv[]) } HMI_DEBUG("HomeScreen","port = %d, token = %s", port, token.toStdString().c_str()); + qDebug() << "HomeScreen - port =" << port << ", token =" << token; // import C++ class to QML // qmlRegisterType<ApplicationLauncher>("HomeScreen", 1, 0, "ApplicationLauncher"); @@ -132,6 +133,8 @@ int main(int argc, char *argv[]) // mail.qml loading QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty("afbPort", port); + engine.rootContext()->setContextProperty("afbToken", token); engine.rootContext()->setContextProperty("layoutHandler", layoutHandler); engine.rootContext()->setContextProperty("homescreenHandler", homescreenHandler); engine.rootContext()->setContextProperty("launcher", launcher); diff --git a/homescreen/src/mastervolume.cpp b/homescreen/src/mastervolume.cpp index 5ef78c5..a4f8d28 100644 --- a/homescreen/src/mastervolume.cpp +++ b/homescreen/src/mastervolume.cpp @@ -15,17 +15,132 @@ */ #include "mastervolume.h" +#include "qafbwsclient.h" +#include "qafbwsmsg.h" +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> +#include <QJsonArray> +#include <QtDebug> +/*! + * @brief Default constructor. + * @param parent Parent's object. + */ +MasterVolume::MasterVolume(QObject* parent) + : QObject{parent} + , m_volume(0) +{ + connect(&m_client, SIGNAL(connected()), this, SLOT(onAfbClientConnected())); + connect(&m_client, SIGNAL(disconnected()), this, SLOT(onAfbClientDisconnected())); +} + +/*! + * @brief Destructor. + */ +MasterVolume::~MasterVolume() +{ +} + +/*! + * @brief Initialize this instance. + * @param port AFB Port. + * @param token AFB Token. + */ +void MasterVolume::init(quint16 port, QString token) +{ + qDebug() << "MasterVolume::init(" << port << "," << token << ")"; + m_client.open(QUrl(QString("ws://localhost:") + QString::number(port) + QString("/api?token=") + token)); +} + +/*! + * \brief Get the current volume. + * @return The volume. + */ +uint32_t MasterVolume::getVolume() const +{ + return m_volume; +} + +/*! + * @brief Set volume. + * @param volume New desired value. + */ void MasterVolume::setVolume(uint32_t volume) { - int volume_delta = volume - m_volume; - m_volume = volume; - emit sliderVolumeChanged(volume_delta); + qDebug() << "MasterVolume::setVolume(" << volume << ")"; + if (volume == m_volumePending) return; // Nothing to do + m_volumePending = volume; + QJsonObject value; + value["label"] = "Master_Playback_Volume"; + value["val"] = static_cast<int>(volume); + + // FIXME: card should be obtained! + if (!m_soundcard.size()) m_soundcard = "rsnddai0ak4613h"; + + m_call = m_client.call(m_soundcard, "ctlset", value); + connect(m_call.data(), SIGNAL(closed()), this, SLOT(onCallCtlSet())); +} + +void MasterVolume::onAfbClientConnected() +{ + qDebug() << "MasterVolume::onAfbClientConnected()"; + m_call = m_client.call("alsacore", "hallist"); + connect(m_call.data(), SIGNAL(closed()), this, SLOT(onCallHalListClosed())); +} + +void MasterVolume::onAfbClientDisconnected() +{ + qDebug() << "MasterVolume::onAfbClientDisconnected()"; +} + +void MasterVolume::onCallHalListClosed() +{ + qDebug() << "MasterVolume::onCallHalListClosed()"; + disconnect(m_call.data(), SIGNAL(closed()), this, SLOT(onCallHalListClosed())); + if (m_call->messageType() == AfMsgType::RetOk && m_call->value().isObject()) + { + m_soundcard = m_call->value().toObject()["response"].toArray()[0].toObject()["api"].toString(); + qDebug() << "MasterVolume::onCallHalListClosed() - Found soundcard api:" << m_soundcard; + } + //m_call.clear(); + + // FIXME: card should be obtained! + if (!m_soundcard.size()) m_soundcard = "rsnddai0ak4613h"; + + // Get volume + if (m_soundcard.size()) + { + QJsonObject arg; + arg["label"] = "Master_Playback_Volume"; + m_call = m_client.call(m_soundcard, "ctlget", arg); + connect(m_call.data(), SIGNAL(closed()), this, SLOT(onCallCtlGet())); + } +} + +void MasterVolume::onCallCtlGet() +{ + qDebug() << "MasterVolume::onCallCtlGet()"; + disconnect(m_call.data(), SIGNAL(closed()), this, SLOT(onCallCtlGet())); + if (m_call->messageType() == AfMsgType::RetOk && m_call->value().isObject()) + { + setVolume(m_call->value().toObject()["response"].toObject()["val"].toArray()[0].toInt()); + } + //m_call.clear(); } -void MasterVolume::changeExternalVolume(int volume) +void MasterVolume::onCallCtlSet() { - m_volume = volume; - emit volumeChanged(); + qDebug() << "MasterVolume::onCallCtlSet()"; + if (m_call.data()) + { + disconnect(m_call.data(), SIGNAL(closed()), this, SLOT(onCallCtlSet())); + if (m_call->messageType() == AfMsgType::RetOk && m_call->value().isObject()) + { + m_volume = m_volumePending; + emit volumeChanged(m_volume); + } + //m_call.clear(); + } } diff --git a/homescreen/src/mastervolume.h b/homescreen/src/mastervolume.h index 3536e58..402b2e5 100644 --- a/homescreen/src/mastervolume.h +++ b/homescreen/src/mastervolume.h @@ -16,6 +16,10 @@ #include <QtCore/QObject> #include <QQmlEngine> +#include <QSharedPointer> + +#include "qafbwsclient.h" +#include "qafbwsmsg.h" class MasterVolume : public QObject @@ -24,24 +28,30 @@ class MasterVolume Q_PROPERTY (uint32_t volume READ getVolume WRITE setVolume NOTIFY volumeChanged) public: - MasterVolume(QObject *parent = 0) - : QObject(parent), m_volume(32768) - { - } + explicit MasterVolume(QObject* parent = nullptr); + ~MasterVolume(); - ~MasterVolume() {} + Q_INVOKABLE void init(quint16 port, QString token); - uint32_t getVolume() const { return m_volume; } - void setVolume(uint32_t volume); + uint32_t getVolume() const; + signals: + void volumeChanged(uint32_t volume); public slots: - void changeExternalVolume(int volume); + void setVolume(uint32_t volume); - signals: - void volumeChanged(void); - void sliderVolumeChanged(int volume_delta); - void externalVolumeChanged(uint32_t volume); + private slots: + void onAfbClientConnected(); + void onAfbClientDisconnected(); + void onCallHalListClosed(); + void onCallCtlGet(); + void onCallCtlSet(); private: uint32_t m_volume; + uint32_t m_volumePending; + QString m_soundcard; + + QAfbWsClient m_client; + QSharedPointer<QAfbWsMsg> m_call; }; diff --git a/homescreen/src/qafbwsclient.cpp b/homescreen/src/qafbwsclient.cpp new file mode 100644 index 0000000..2816907 --- /dev/null +++ b/homescreen/src/qafbwsclient.cpp @@ -0,0 +1,136 @@ +#include "qafbwsclient.h" +#include "qafbwsmsg.h" +#include <QJsonDocument> +#include <QJsonArray> +#include <QtDebug> + +QAfbWsClient::QAfbWsClient(QObject* parent) + : QObject{parent} + , m_nextCallId{0} +{ + connect(&m_socket, SIGNAL(connected()), this, SLOT(onSocketConnected())); + connect(&m_socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + connect(&m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError))); +} + +QUrl QAfbWsClient::url() const +{ + return m_socket.requestUrl(); +} + +QSharedPointer<QAfbWsMsg> QAfbWsClient::call(const QString& api, const QString& verb, const QJsonValue& args) +{ + while(m_calls.constFind(m_nextCallId) != m_calls.cend()) m_nextCallId++; // Make sure that callId is not currently used + + qDebug() << "QAfbWsClient::call(" << api << ", " << verb << ", " << args << ")"; + QJsonArray arr; + arr.append(static_cast<int>(AfMsgType::Call)); + arr.append(m_nextCallId); + arr.append(api + "/" + verb); + arr.append(args); + + QJsonDocument doc; + doc.setArray(arr); + + QSharedPointer<QAfbWsMsg> msg(new QAfbWsMsg(m_nextCallId, api, verb)); + m_calls[m_nextCallId] = msg; + + // TODO: handle failure of sendTextMessage + m_socket.sendTextMessage(doc.toJson(QJsonDocument::Compact)); + qDebug() << "m_socket.sendTextMessage(" << doc.toJson(QJsonDocument::Compact) << ")"; + + m_nextCallId++; + + return msg; +} + +void QAfbWsClient::onSocketConnected() +{ + qDebug() << "QAfbWsClient::onSocketConnected()"; + connect(&m_socket, SIGNAL(textMessageReceived(QString)), this, SLOT(onSocketTextMessageReceived(QString))); + emit connected(); +} + +void QAfbWsClient::onSocketDisconnected() +{ + qDebug() << "QAfbWsClient::onSocketDisconnected()"; + m_nextCallId = 0; + disconnect(&m_socket, SIGNAL(textMessageReceived(QString)), this, SLOT(onSocketTextMessageReceived(QString))); + emit disconnected(); +} + +void QAfbWsClient::onSocketError(QAbstractSocket::SocketError err) +{ + qDebug() << "QAfbWsClient::onSocketError(" << err << ")"; + emit error(err, m_socket.errorString()); +} + +void QAfbWsClient::onSocketTextMessageReceived(QString msg) +{ + qDebug() << "QAfbWsClient::onSocketTextMessageReceived(" << msg << ")"; + + if (msg.size() == 0) + { + qCritical() << "QAfbWsClient::onSocketTextMessageReceived: received an empty message."; + return; + } + + QJsonDocument doc = QJsonDocument::fromJson(msg.toUtf8()); + if (doc.isEmpty() || doc.isNull() || !doc.isArray()) + { + qCritical() << "QAfbWsClient::onSocketTextMessageReceived: received an invalid message."; + return; + } + + QJsonArray arr = doc.array(); + AfMsgType msgType = static_cast<AfMsgType>(arr[0].toInt()); + int callId = arr[1].isString() ? arr[1].toString().toInt() : arr[1].toInt(); + QString api; + QJsonValue value; + + switch(msgType) + { + case AfMsgType::Call: + qCritical() << "QAfbWsClient::onSocketTextMessageReceived: Client received a call, which should not happen."; + break; + + case AfMsgType::Event: + qDebug() << "QAfbWsClient::onSocketTextMessageReceived: Client received an event."; + // TODO: handle events + value = arr[3]; + break; + + case AfMsgType::RetOk: + case AfMsgType::RetErr: + { + value = arr[2]; + QMap<int, QSharedPointer<QAfbWsMsg>>::const_iterator it = m_calls.find(callId); + if (it == m_calls.end()) + { + qCritical() << "QAfbWsClient::onSocketTextMessageReceived: received a response to a not found query."; + return; + } + + QSharedPointer<QAfbWsMsg> m = *it; + m_calls.remove(callId); + m->close(msgType, value); + } + break; + + default: + qCritical() << "QAfbWsClient::onSocketTextMessageReceived: Client received an unsupported message."; + break; + } +} + +void QAfbWsClient::open(const QUrl& url) +{ + qDebug() << "QAfbWsClient::open(" << url << ")"; + m_socket.open(url); +} + +void QAfbWsClient::close() +{ + qDebug() << "QAfbWsClient::close()"; + m_socket.close(); +} diff --git a/homescreen/src/qafbwsclient.h b/homescreen/src/qafbwsclient.h new file mode 100644 index 0000000..2576ecb --- /dev/null +++ b/homescreen/src/qafbwsclient.h @@ -0,0 +1,50 @@ +#ifndef QAFBWSCLIENT_H +#define QAFBWSCLIENT_H + +#include <QObject> +#include <QMap> +#include <QSharedPointer> +#include <QWebSocket> +#include <QJsonValue> + +enum class AfMsgType + : int +{ + Call = 2, + RetOk = 3, + RetErr = 4, + Event = 5 +}; + +class QAfbWsMsg; + +class QAfbWsClient + : public QObject +{ + Q_OBJECT +public: + explicit QAfbWsClient(QObject* parent = nullptr); + + void open(const QUrl& url); + void close(); + QUrl url() const; + QSharedPointer<QAfbWsMsg> call(const QString &api, const QString &verb, const QJsonValue &args = QJsonValue()); + +signals: + void connected(); + void disconnected(); + void error(QAbstractSocket::SocketError err, QString errStr); + +private slots: + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketError(QAbstractSocket::SocketError err); + void onSocketTextMessageReceived(QString msg); + +private: + int m_nextCallId; + QWebSocket m_socket; + QMap<int, QSharedPointer<QAfbWsMsg>> m_calls; +}; + +#endif // QAFBWSCLIENT_H diff --git a/homescreen/src/qafbwsmsg.cpp b/homescreen/src/qafbwsmsg.cpp new file mode 100644 index 0000000..3f07cb0 --- /dev/null +++ b/homescreen/src/qafbwsmsg.cpp @@ -0,0 +1,44 @@ +#include "qafbwsmsg.h" + +QAfbWsMsg::QAfbWsMsg(int callId, const QString& api, const QString& verb, QObject* parent) + : QObject{parent} + , m_callId{callId} +{ + m_api = api; + m_verb = verb; +} + +void QAfbWsMsg::close(AfMsgType type, const QJsonValue& result) +{ + m_type = type; + m_value = result; + + qDebug() << "QAfbWsMsg::close: type=" << static_cast<int>(m_type) << ", api=" << m_api << ", verb=" << m_verb << ", value=" << m_value; + + emit closed(); +} + +int QAfbWsMsg::callId() const +{ + return m_callId; +} + +AfMsgType QAfbWsMsg::messageType() const +{ + return m_type; +} + +QString QAfbWsMsg::api() const +{ + return m_api; +} + +QString QAfbWsMsg::verb() const +{ + return m_verb; +} + +QJsonValue QAfbWsMsg::value() const +{ + return m_value; +} diff --git a/homescreen/src/qafbwsmsg.h b/homescreen/src/qafbwsmsg.h new file mode 100644 index 0000000..6acce59 --- /dev/null +++ b/homescreen/src/qafbwsmsg.h @@ -0,0 +1,33 @@ +#ifndef QAFBWSMSG_H +#define QAFBWSMSG_H + +#include <QObject> +#include <QJsonValue> +#include "qafbwsclient.h" + +class QAfbWsMsg + : public QObject +{ + Q_OBJECT +public: + explicit QAfbWsMsg(int callId = 0, const QString& api = "", const QString& verb = "", QObject* parent = nullptr); + void close(AfMsgType type, const QJsonValue& result); + + int callId() const; + AfMsgType messageType() const; + QString api() const; + QString verb() const; + QJsonValue value() const; + +signals: + void closed(); + +private: + int m_callId; + AfMsgType m_type; + QString m_api; + QString m_verb; + QJsonValue m_value; +}; + +#endif // QAFBWSMSG_H diff --git a/package/config.xml b/package/config.xml index 885473c..c3f8846 100644 --- a/package/config.xml +++ b/package/config.xml @@ -9,6 +9,9 @@ <feature name="urn:AGL:widget:required-api"> <param name="homescreen" value="ws" /> <param name="windowmanager" value="ws" /> + <param name="ahl-4a" value="ws" /> + <param name="alsacore" value="ws" /> + <param name="rsnddai0ak4613h" value="ws" /> </feature> <feature name="urn:AGL:widget:required-permission"> <param name="urn:AGL:permission::public:no-htdocs" value="required" /> |