From 34b97e89d4bf565cabab23141e9f920d9ec8e83c Mon Sep 17 00:00:00 2001 From: nymea Date: Thu, 29 Aug 2019 21:10:32 +0200 Subject: [PATCH] added new nymea auth method --- sonos/devicepluginsonos.cpp | 164 +++++++++++++++++++++--------- sonos/devicepluginsonos.h | 8 +- sonos/devicepluginsonos.json | 6 +- sonos/sonos.cpp | 190 ++++++++++++++++++++++++++++++++--- sonos/sonos.h | 17 +--- sonos/sonos.pro | 2 - 6 files changed, 304 insertions(+), 83 deletions(-) diff --git a/sonos/devicepluginsonos.cpp b/sonos/devicepluginsonos.cpp index 24f61306..bfdbabca 100644 --- a/sonos/devicepluginsonos.cpp +++ b/sonos/devicepluginsonos.cpp @@ -22,10 +22,13 @@ #include "devicepluginsonos.h" #include "devices/device.h" +#include "network/networkaccessmanager.h" #include "plugininfo.h" #include #include +#include +#include DevicePluginSonos::DevicePluginSonos() { @@ -45,23 +48,13 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) } if (device->deviceClassId() == sonosConnectionDeviceClassId) { - - Sonos *sonos = new Sonos("0a8f6d44-d9d1-4474-bcfa-cfb41f8b66e8", this); + qCDebug(dcSonos()) << "Sonos OAuth setup complete"; + Sonos *sonos = new Sonos(hardwareManager()->networkManager(), "0a8f6d44-d9d1-4474-bcfa-cfb41f8b66e8", this); pluginStorage()->beginGroup(device->id().toString()); QString username = pluginStorage()->value("username").toString(); QString password = pluginStorage()->value("password").toString(); pluginStorage()->endGroup(); - - sonos->authenticate(username, password); - - m_sonosConnections.insert(device->id(), sonos); - connect(sonos, &Sonos::authenticationFinished, this, [this, sonos](bool success){ - if (success) { - } else { - qCWarning(dcSonos()) << "Cannot authenticate to Sonos api"; - } - }); - + m_sonosConnections.insert(device, sonos); } if (device->deviceClassId() == sonosGroupDeviceClassId) { @@ -71,6 +64,88 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) return Device::DeviceSetupStatusSuccess; } +DevicePairingInfo DevicePluginSonos::pairDevice(DevicePairingInfo &devicePairingInfo) +{ + + if (devicePairingInfo.deviceClassId() == sonosConnectionDeviceClassId) { + QString clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; + QString clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11"; + + QUrl url("https://api.sonos.com/login/v3/oauth"); + QUrlQuery queryParams; + queryParams.addQueryItem("client_id", clientId); + queryParams.addQueryItem("redirect_uri", "https://127.0.0.1:8888"); + queryParams.addQueryItem("response_type", "code"); + queryParams.addQueryItem("scope", "playback-control-all"); + queryParams.addQueryItem("state", QUuid::createUuid().toString()); + url.setQuery(queryParams); + + qCDebug(dcSonos()) << "Sonos url:" << url; + + devicePairingInfo.setOAuthUrl(url); + devicePairingInfo.setStatus(Device::DeviceErrorNoError); + return devicePairingInfo; + } + + qCWarning(dcSonos()) << "Unhandled pairing metod!"; + devicePairingInfo.setStatus(Device::DeviceErrorCreationMethodNotSupported); + return devicePairingInfo; +} + +DevicePairingInfo DevicePluginSonos::confirmPairing(DevicePairingInfo &devicePairingInfo, const QString &username, const QString &secret) +{ + Q_UNUSED(username); + qCDebug(dcSonos()) << "Confirm pairing"; + + if (devicePairingInfo.deviceClassId() == sonosConnectionDeviceClassId) { + qCDebug(dcSonos()) << "Secret is" << secret; + QUrl url(secret); + QUrlQuery query(url); + qCDebug(dcSonos()) << "Acess code is:" << query.queryItemValue("code"); + + QString accessCode = query.queryItemValue("code"); + + // Obtaining access token + url = QUrl("https://api.sonos.com/login/v3/oauth/access"); + query.clear(); + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", accessCode); + query.addQueryItem("redirect_uri", "https%3A%2F%2F127.0.0.1%3A8888"); + url.setQuery(query); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=utf-8"); + + QByteArray clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; + QByteArray clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11"; + + QByteArray auth = QByteArray(clientId + ':' + clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals); + request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8()); + + QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray()); + connect(reply, &QNetworkReply::finished, this, [this, reply, devicePairingInfo](){ + reply->deleteLater(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); + qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson(); + qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString(); + qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(jsonDoc.toVariant().toMap().value("expires_in").toInt()).toString(); + qCDebug(dcSonos()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString(); + DevicePairingInfo info(devicePairingInfo); + info.setStatus(Device::DeviceErrorNoError); + emit pairingFinished(info); + }); + + devicePairingInfo.setStatus(Device::DeviceErrorAsync); + return devicePairingInfo; + } + + + qCWarning(dcSonos()) << "Invalid deviceclassId -> no pairing possible with this device"; + devicePairingInfo.setStatus(Device::DeviceErrorHardwareFailure); + return devicePairingInfo; +} + void DevicePluginSonos::postSetupDevice(Device *device) { if (device->deviceClassId() == sonosConnectionDeviceClassId) { @@ -82,6 +157,16 @@ void DevicePluginSonos::postSetupDevice(Device *device) } } + +void DevicePluginSonos::startMonitoringAutoDevices() +{ + foreach (Device *device, myDevices()) { + if (device->deviceClassId() == sonosGroupDeviceClassId) { + return; // We already have a Auto Mock device... do nothing. + } + } +} + void DevicePluginSonos::deviceRemoved(Device *device) { qCDebug(dcSonos) << "Delete " << device->name(); @@ -94,8 +179,8 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio { Q_UNUSED(action) if (device->deviceClassId() == sonosGroupDeviceClassId) { - Sonos *sonos = m_sonosConnections.value(device->parentId()); - int groupId = device->paramValue(sonosGroupDe) + Sonos *sonos = m_sonosConnections.value(device); + //int groupId = device->paramValue(sonosGroupDe) if (!sonos) return Device::DeviceErrorInvalidParameter; @@ -131,41 +216,33 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio } if (action.actionTypeId() == sonosGroupStopActionTypeId) { - sonos->stop(); + //sonos->stop(); return Device::DeviceErrorNoError; } if (action.actionTypeId() == sonosGroupMuteActionTypeId) { - bool mute = action.param(sonosGroupMuteActionMuteParamTypeId).value().toBool(); + //bool mute = action.param(sonosGroupMuteActionMuteParamTypeId).value().toBool(); - sonos->setGroupMute() + //sonos->setGroupMute(); return Device::DeviceErrorNoError; } - if (action.actionTypeId() == sonosSkipNextActionTypeId) { - if (!m_sonosSystem->GetPlayer()->Next()) { + if (action.actionTypeId() == sonosGroupSkipNextActionTypeId) { + /*if (!m_sonosSystem->GetPlayer()->Next()) { return Device::DeviceErrorHardwareFailure; - } + }*/ return Device::DeviceErrorNoError; } - if (action.actionTypeId() == sonosSkipBackActionTypeId) { - if(!m_sonosSystem->GetPlayer()->Previous()) { + if (action.actionTypeId() == sonosGroupSkipBackActionTypeId) { + /* if(!m_sonosSystem->GetPlayer()->Previous()) { return Device::DeviceErrorHardwareFailure; - } + }*/ return Device::DeviceErrorNoError; } - if (action.actionTypeId() == sonosSkipBackActionTypeId) { - int volume = action.param(sonosVolumeActionVolumeParamTypeId).value().toInt(); - - SONOS::ZonePtr pl = m_sonosSystem->GetConnectedZone(); - for (SONOS::Zone::iterator ip = pl->begin(); ip != pl->end(); ++ip) { - if (!m_sonosSystem->GetPlayer()->SetVolume((*ip)->GetUUID(), volume)) { - qWarning(dcSonos()) << "Could not set volume for" << (*ip)->GetHost().c_str(); - return Device::DeviceErrorHardwareFailure; - } - } + if (action.actionTypeId() == sonosGroupSkipBackActionTypeId) { + //int volume = action.param(sonosVolumeActionVolumeParamTypeId).value().toInt(); return Device::DeviceErrorNoError; } return Device::DeviceErrorActionTypeNotFound; @@ -177,18 +254,11 @@ void DevicePluginSonos::onPluginTimer() { } -void DevicePluginSonos::handleEventCB(void* handle) +void DevicePluginSonos::onConnectionChanged() { - Q_UNUSED(handle); - /*unsigned char mask = m_sonosSystem->LastEvents(); - if ((mask & SONOS::SVCEvent_TransportChanged)) - qDebug(dcSonos()) << "Event Transport changed"; - if ((mask & SONOS::SVCEvent_AlarmClockChanged)) - qDebug(dcSonos()) << "Alarm clock changed"; - if ((mask & SONOS::SVCEvent_ZGTopologyChanged)) - qDebug(dcSonos()) << "ZG Topology changed"; - if ((mask & SONOS::SVCEvent_ContentDirectoryChanged)) - qDebug(dcSonos()) << "Content directory changed"; - if ((mask & SONOS::SVCEvent_RenderingControlChanged)) - qDebug(dcSonos()) << "Rendering control changed";*/ + Sonos *sonos = static_cast(sender()); + Device *device = m_sonosConnections.key(sonos); + device->setStateValue(sonosConnectionConnectedStateTypeId, false); //TODO + + //TODO set all groups } diff --git a/sonos/devicepluginsonos.h b/sonos/devicepluginsonos.h index 3fb8f1da..db84374b 100644 --- a/sonos/devicepluginsonos.h +++ b/sonos/devicepluginsonos.h @@ -42,15 +42,17 @@ public: ~DevicePluginSonos() override; Device::DeviceSetupStatus setupDevice(Device *device) override; + DevicePairingInfo pairDevice(DevicePairingInfo &devicePairingInfo) override; + DevicePairingInfo confirmPairing(DevicePairingInfo &devicePairingInfo, const QString &username, const QString &secret) override; + void postSetupDevice(Device *device) override; + void startMonitoringAutoDevices() override; void deviceRemoved(Device *device) override; - Device::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override; Device::DeviceError executeAction(Device *device, const Action &action) override; private: PluginTimer *m_pluginTimer = nullptr; - QHash m_sonosConnections; - + QHash m_sonosConnections; private slots: void onPluginTimer(); diff --git a/sonos/devicepluginsonos.json b/sonos/devicepluginsonos.json index 68daca04..a59bd854 100644 --- a/sonos/devicepluginsonos.json +++ b/sonos/devicepluginsonos.json @@ -15,7 +15,7 @@ "displayName": "Sonos connection", "interfaces": ["gateway"], "createMethods": ["user"], - "setupMethod": "userandpassword", + "setupMethod": "oauth", "stateTypes": [ { "id": "5aa4360c-61de-47d0-a72e-a19d57712e1c", @@ -28,11 +28,11 @@ ] }, { - "id": "22df416d-7732-44f1-b6b9-e41296211178", + "id": "72d9332b-2b25-4136-87a6-e534eae4cc80", "name": "sonosGroup", "displayName": "Sonos group", "interfaces": ["extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"], - "createMethods": ["discovery"], + "createMethods": ["auto"], "paramTypes": [ { "id": "defc44cd-2ffb-4af1-b348-d6a3474c7515", diff --git a/sonos/sonos.cpp b/sonos/sonos.cpp index 3263085b..20cf52dc 100644 --- a/sonos/sonos.cpp +++ b/sonos/sonos.cpp @@ -22,35 +22,30 @@ #include "sonos.h" #include "extern-plugininfo.h" -#include "network/networkaccessmanager.h" #include -Sonos::Sonos(QByteArray apiKey, QObject *parent) : +Sonos::Sonos(NetworkAccessManager *networkmanager, const QByteArray &accessToken, QObject *parent) : QObject(parent), - m_apiKey(apiKey) + m_accessToken(accessToken), + m_networkManager(networkmanager) { } -void Sonos::authenticate(const QString &username, const QString &password) +void Sonos::setAccessToken(const QByteArray &accessToken) { - Q_UNUSED(username) - Q_UNUSED(password) - - m_OAuth = new OAuth(m_apiKey, this); - m_OAuth->setUrl(QUrl(m_baseAuthorizationUrl)); - m_OAuth->setScope("playback-control-all"); - m_OAuth->startAuthentication(); + m_accessToken = accessToken; } void Sonos::getHouseholds() { QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); - request.setRawHeader("Authorization", "Bearer" + m_OAuth->bearerToken()); + request.setRawHeader("Authorization", "Bearer" + m_accessToken); request.setUrl(QUrl(m_baseControlUrl + "/households")); - QNetworkReply *reply = QNetworkAccessManager.get(request); - connect(reply, &QNetworkReply::finished, this [this] { + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, this] { + reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // Check HTTP status code @@ -58,13 +53,176 @@ void Sonos::getHouseholds() qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); return; } - QJsonDocument data = reply->readAll(); + + qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + /*QJsonDocument data = reply->readAll(); if (!data.isObject()) return; QList households; - emit householdObjectsReceived(households); + emit householdObjectsReceived(households);*/ }); } +void Sonos::cancelAudioClip() +{ + +} + +void Sonos::loadAudioClip() +{ + +} + +void Sonos::getFavorites() +{ + +} + +void Sonos::loadFavorite() +{ + +} + +void Sonos::getGroups() +{ + +} + +void Sonos::createGroup() +{ + +} + +void Sonos::modifyGroupMembers() +{ + +} + +void Sonos::setGroupMembers() +{ + +} + +void Sonos::getGroupVolume(int groupId) +{ + Q_UNUSED(groupId) +} + +void Sonos::setGroupVolume(int groupId, int volume) +{ + Q_UNUSED(groupId) + Q_UNUSED(volume) +} + +void Sonos::setGroupMute(int groupId, bool mute) +{ + Q_UNUSED(groupId) + Q_UNUSED(mute) +} + +void Sonos::setGroupRelativeVolume(int groupId, int volumeDelta) +{ + Q_UNUSED(groupId) + Q_UNUSED(volumeDelta) +} + +void Sonos::getPlaybackStatus() +{ + +} + +void Sonos::loadLineIn() +{ + +} + +void Sonos::play() +{ + +} + +void Sonos::pause() +{ + +} + +void Sonos::seek() +{ + +} + +void Sonos::seekRelative() +{ + +} + +void Sonos::setPlayModes() +{ + +} + +void Sonos::skipToNextTrack() +{ + +} + +void Sonos::skipToPreviousTrack() +{ + +} + +void Sonos::togglePlayPause() +{ + +} + +void Sonos::getPlayerVolume(int playerId) +{ + Q_UNUSED(playerId) +} + +void Sonos::setPlayerVolume(int playerId, int volume) +{ + Q_UNUSED(playerId) + Q_UNUSED(volume) +} + +void Sonos::setPlayerRelativeVolume(int playerId, int volumeDelta) +{ + Q_UNUSED(playerId) + Q_UNUSED(volumeDelta) +} + +void Sonos::setPlayerMute(int playerId, bool mute) +{ + Q_UNUSED(playerId) + Q_UNUSED(mute) +} + +void Sonos::getPlaylist() +{ + +} + +void Sonos::getPlaylists() +{ + +} + +void Sonos::loadPlaylist() +{ + +} + +void Sonos::getPlayerSettings() +{ + +} + +void Sonos::setPlayerSettings() +{ + +} + diff --git a/sonos/sonos.h b/sonos/sonos.h index 8ff3e0c4..4d98478a 100644 --- a/sonos/sonos.h +++ b/sonos/sonos.h @@ -24,10 +24,9 @@ #define SONOS_H #include -#include +#include "network/networkaccessmanager.h" #include "devices/device.h" -#include "oauth.h" class Sonos : public QObject { @@ -149,10 +148,9 @@ public: //tags enum }; - explicit Sonos(QByteArray apiKey, QObject *parent = nullptr); + explicit Sonos(NetworkAccessManager *networkManager, const QByteArray &accessToken, QObject *parent = nullptr); - void setApiKey(QByteArray apiKey); - void authenticate(const QString &username, const QString &password); + void setAccessToken(const QByteArray &accessToken); void getHouseholds(); @@ -205,19 +203,14 @@ public: private: QByteArray m_baseAuthorizationUrl = "api.sonos.com/login/v3/oauth"; QByteArray m_baseControlUrl = "api.ws.sonos.com/control/api/v1"; - QByteArray m_apiKey; + QByteArray m_accessToken; - OAuth *m_OAuth = nullptr; + NetworkAccessManager *m_networkManager = nullptr; private slots: signals: - void authenticationFinished(); - void authenticationFailed(const QString &reason); - - -public slots: }; diff --git a/sonos/sonos.pro b/sonos/sonos.pro index 7043560f..a107f02e 100644 --- a/sonos/sonos.pro +++ b/sonos/sonos.pro @@ -7,11 +7,9 @@ TARGET = $$qtLibraryTarget(nymea_devicepluginsonos) SOURCES += \ devicepluginsonos.cpp \ sonos.cpp \ - oauth.cpp HEADERS += \ devicepluginsonos.h \ sonos.h \ - oauth.h