added new nymea auth method

master
nymea 2019-08-29 21:10:32 +02:00 committed by Michael Zanetti
parent 85060d152c
commit 34b97e89d4
6 changed files with 304 additions and 83 deletions

View File

@ -22,10 +22,13 @@
#include "devicepluginsonos.h"
#include "devices/device.h"
#include "network/networkaccessmanager.h"
#include "plugininfo.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonDocument>
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<Sonos *>(sender());
Device *device = m_sonosConnections.key(sonos);
device->setStateValue(sonosConnectionConnectedStateTypeId, false); //TODO
//TODO set all groups
}

View File

@ -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 &params) override;
Device::DeviceError executeAction(Device *device, const Action &action) override;
private:
PluginTimer *m_pluginTimer = nullptr;
QHash<DeviceId, Sonos *> m_sonosConnections;
QHash<Device *, Sonos *> m_sonosConnections;
private slots:
void onPluginTimer();

View File

@ -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",

View File

@ -22,35 +22,30 @@
#include "sonos.h"
#include "extern-plugininfo.h"
#include "network/networkaccessmanager.h"
#include <QJsonDocument>
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<HouseholdObject> 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()
{
}

View File

@ -24,10 +24,9 @@
#define SONOS_H
#include <QObject>
#include <QNetworkAccessManager>
#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:
};

View File

@ -7,11 +7,9 @@ TARGET = $$qtLibraryTarget(nymea_devicepluginsonos)
SOURCES += \
devicepluginsonos.cpp \
sonos.cpp \
oauth.cpp
HEADERS += \
devicepluginsonos.h \
sonos.h \
oauth.h