added plugin Storage for tokens

This commit is contained in:
nymea 2019-09-01 21:41:10 +02:00 committed by Michael Zanetti
parent 34b97e89d4
commit 4254d3cb3c
5 changed files with 283 additions and 145 deletions

View File

@ -45,15 +45,23 @@ DevicePluginSonos::~DevicePluginSonos()
Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device)
{
if (!m_pluginTimer) {
hardwareManager()->pluginTimerManager()->registerTimer(1);
}
if(!m_tokenRefreshTimer) {
m_tokenRefreshTimer = new QTimer(this);
m_tokenRefreshTimer->setSingleShot(false);
connect(m_tokenRefreshTimer, &QTimer::timeout, this, &DevicePluginSonos::onRefreshTimeout);
}
if (device->deviceClassId() == sonosConnectionDeviceClassId) {
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();
QByteArray accessToken = pluginStorage()->value("access_token").toByteArray();
pluginStorage()->endGroup();
Sonos *sonos = new Sonos(hardwareManager()->networkManager(), accessToken, this);
m_sonosConnections.insert(device, sonos);
}
@ -98,49 +106,66 @@ DevicePairingInfo DevicePluginSonos::confirmPairing(DevicePairingInfo &devicePai
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");
qCDebug(dcSonos()) << "Secret is" << secret;
QUrl url(secret);
QUrlQuery query(url);
qCDebug(dcSonos()) << "Acess code is:" << query.queryItemValue("code");
QString accessCode = 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);
// 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");
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 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());
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();
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
connect(reply, &QNetworkReply::finished, this, [this, reply, devicePairingInfo](){
reply->deleteLater();
DevicePairingInfo info(devicePairingInfo);
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);
});
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) {
info.setStatus(Device::DeviceErrorSetupFailed);
emit pairingFinished(info);
return;
}
qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
QByteArray accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
devicePairingInfo.setStatus(Device::DeviceErrorAsync);
return devicePairingInfo;
}
qCDebug(dcSonos()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString();
QByteArray refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray();
pluginStorage()->beginGroup(info.deviceId().toString());
pluginStorage()->setValue("access_token", accessToken);
pluginStorage()->setValue("refresh_token", refreshToken);
pluginStorage()->endGroup();
/*if (jsonDoc.toVariant().toMap().contains("expires_in")) {
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
}*/
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;
@ -149,7 +174,8 @@ DevicePairingInfo DevicePluginSonos::confirmPairing(DevicePairingInfo &devicePai
void DevicePluginSonos::postSetupDevice(Device *device)
{
if (device->deviceClassId() == sonosConnectionDeviceClassId) {
Sonos *sonos = m_sonosConnections.value(device);
sonos->getHouseholds();
}
if (device->deviceClassId() == sonosGroupDeviceClassId) {
@ -180,69 +206,59 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio
Q_UNUSED(action)
if (device->deviceClassId() == sonosGroupDeviceClassId) {
Sonos *sonos = m_sonosConnections.value(device);
//int groupId = device->paramValue(sonosGroupDe)
QByteArray groupId = device->paramValue(sonosGroupDeviceGroupIdParamTypeId).toByteArray();
if (!sonos)
return Device::DeviceErrorInvalidParameter;
if (action.actionTypeId() == sonosGroupPlayActionTypeId) {
sonos->play();
sonos->groupPlay(groupId);
return Device::DeviceErrorAsync;
}
if (action.actionTypeId() == sonosGroupShuffleActionTypeId) {
bool shuffle = action.param(sonosGroupShuffleActionShuffleParamTypeId).value().toBool();
sonos->groupSetShuffle(groupId, shuffle);
return Device::DeviceErrorAsync;
}
if (action.actionTypeId() == sonosGroupRepeatActionTypeId) {
if (action.param(sonosGroupRepeatActionRepeatParamTypeId).value().toString() == "None") {
sonos->groupSetRepeat(groupId, Sonos::RepeatModeNone);
} else if (action.param(sonosGroupShuffleActionShuffleParamTypeId).value().toString() == "One") {
sonos->groupSetRepeat(groupId, Sonos::RepeatModeOne);
} else if (action.param(sonosGroupShuffleActionShuffleParamTypeId).value().toString() == "All") {
sonos->groupSetRepeat(groupId, Sonos::RepeatModeAll);
} else {
return Device::DeviceErrorHardwareFailure;
}
return Device::DeviceErrorAsync;
}
if (action.actionTypeId() == sonosGroupPauseActionTypeId) {
sonos->pause();
sonos->groupPause(groupId);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == sonosGroupStopActionTypeId) {
//sonos->stop();
sonos->groupPause(groupId);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == sonosGroupMuteActionTypeId) {
//bool mute = action.param(sonosGroupMuteActionMuteParamTypeId).value().toBool();
//sonos->setGroupMute();
bool mute = action.param(sonosGroupMuteActionMuteParamTypeId).value().toBool();
sonos->setGroupMute(groupId, mute);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == sonosGroupSkipNextActionTypeId) {
/*if (!m_sonosSystem->GetPlayer()->Next()) {
return Device::DeviceErrorHardwareFailure;
}*/
sonos->groupSkipToNextTrack(groupId);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == sonosGroupSkipBackActionTypeId) {
/* if(!m_sonosSystem->GetPlayer()->Previous()) {
return Device::DeviceErrorHardwareFailure;
}*/
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == sonosGroupSkipBackActionTypeId) {
//int volume = action.param(sonosVolumeActionVolumeParamTypeId).value().toInt();
sonos->groupSkipToPreviousTrack(groupId);
return Device::DeviceErrorNoError;
}
return Device::DeviceErrorActionTypeNotFound;
@ -262,3 +278,43 @@ void DevicePluginSonos::onConnectionChanged()
//TODO set all groups
}
void DevicePluginSonos::onRefreshTimeout()
{
qCDebug(dcSonos) << "Refresh authentication token";
QUrlQuery query;
query.addQueryItem("grant_type", "refresh_token");
query.addQueryItem("refresh_token", m_sonosConnectionRefreshToken);
QUrl url("https://api.sonos.com/login/v3/oauth");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8");
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
connect(reply, &QNetworkReply::finished, this, [this, reply](){
reply->deleteLater();
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
if(!jsonDoc.toVariant().toMap().contains("access_token")) {
return;
}
qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
m_sonosConnectionAccessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
if (jsonDoc.toVariant().toMap().contains("expires_in")) {
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
}
});
}
void DevicePluginSonos::onHouseholdIdsReceived(QList<QByteArray> householdIds)
{
qDebug(dcSonos()) << "Household Id received, start to discover groups";
Sonos *sonos = static_cast<Sonos *>(sender());
foreach(QByteArray householdId, householdIds) {
sonos->getGroups(householdId);
}
}

View File

@ -29,6 +29,7 @@
#include <QHash>
#include <QDebug>
#include <QTimer>
class DevicePluginSonos : public DevicePlugin
@ -52,11 +53,20 @@ public:
private:
PluginTimer *m_pluginTimer = nullptr;
QTimer *m_tokenRefreshTimer = nullptr;
QHash<Device *, Sonos *> m_sonosConnections;
QList<QByteArray> m_householdIds;
QByteArray m_sonosConnectionAccessToken;
QByteArray m_sonosConnectionRefreshToken;
private slots:
void onPluginTimer();
void onConnectionChanged();
void onRefreshTimeout();
void onHouseholdIdsReceived(QList<QByteArray> householdIds);
};

View File

@ -16,6 +16,8 @@
"interfaces": ["gateway"],
"createMethods": ["user"],
"setupMethod": "oauth",
"paramTypes": [
],
"stateTypes": [
{
"id": "5aa4360c-61de-47d0-a72e-a19d57712e1c",

View File

@ -41,9 +41,11 @@ void Sonos::getHouseholds()
{
QNetworkRequest request;
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", "Bearer" + m_accessToken);
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
request.setUrl(QUrl(m_baseControlUrl + "/households"));
QNetworkReply *reply = m_networkManager->get(request);
qDebug(dcSonos()) << "Sending request" << request.url() << request.rawHeaderList() << request.rawHeader("Authorization");
connect(reply, &QNetworkReply::finished, this, [reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -58,8 +60,6 @@ void Sonos::getHouseholds()
/*QJsonDocument data = reply->readAll();
if (!data.isObject())
return;
QList<HouseholdObject> households;
emit householdObjectsReceived(households);*/
});
@ -85,14 +85,38 @@ void Sonos::loadFavorite()
}
void Sonos::getGroups()
void Sonos::getGroups(const QByteArray &householdId)
{
QNetworkRequest request;
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/groups"));
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
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcSonos()) << "Request error:" << status << reply->errorString();
return;
}
qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll();
/*QJsonDocument data = reply->readAll();
if (!data.isObject())
return;
QList<HouseholdObject> households;
emit householdObjectsReceived(households);*/
});
}
void Sonos::createGroup()
void Sonos::createGroup(const QByteArray &householdId, QList<QByteArray> playerIds)
{
Q_UNUSED(householdId)
Q_UNUSED(playerIds)
}
void Sonos::modifyGroupMembers()
@ -100,105 +124,126 @@ void Sonos::modifyGroupMembers()
}
void Sonos::setGroupMembers()
{
}
void Sonos::getGroupVolume(int groupId)
void Sonos::setGroupMembers(const QByteArray &groupId)
{
Q_UNUSED(groupId)
}
void Sonos::setGroupVolume(int groupId, int volume)
void Sonos::getGroupVolume(const QByteArray &groupId)
{
Q_UNUSED(groupId)
Q_UNUSED(volume)
Q_UNUSED(groupId)
}
void Sonos::setGroupMute(int groupId, bool mute)
void Sonos::setGroupVolume(const QByteArray &groupId, int volume)
{
Q_UNUSED(groupId)
Q_UNUSED(mute)
Q_UNUSED(groupId)
Q_UNUSED(volume)
}
void Sonos::setGroupRelativeVolume(int groupId, int volumeDelta)
void Sonos::setGroupMute(const QByteArray &groupId, bool mute)
{
Q_UNUSED(groupId)
Q_UNUSED(volumeDelta)
Q_UNUSED(groupId)
Q_UNUSED(mute)
}
void Sonos::getPlaybackStatus()
void Sonos::setGroupRelativeVolume(const QByteArray &groupId, int volumeDelta)
{
Q_UNUSED(groupId)
Q_UNUSED(volumeDelta)
}
void Sonos::getGroupPlaybackStatus(const QByteArray &groupId)
{
Q_UNUSED(groupId)
}
void Sonos::groupLoadLineIn(const QByteArray &groupId)
{
Q_UNUSED(groupId)
}
void Sonos::loadLineIn()
void Sonos::groupPlay(const QByteArray &groupId)
{
Q_UNUSED(groupId)
}
void Sonos::play()
void Sonos::groupPause(const QByteArray &groupId)
{
Q_UNUSED(groupId)
}
void Sonos::pause()
void Sonos::groupSeek(const QByteArray &groupId)
{
Q_UNUSED(groupId)
}
void Sonos::seek()
void Sonos::groupSeekRelative(const QByteArray &groupId, int millis)
{
Q_UNUSED(groupId)
Q_UNUSED(millis)
}
void Sonos::seekRelative()
void Sonos::groupSetPlayModes(const QByteArray &groupId, PlayMode playMode)
{
Q_UNUSED(groupId)
Q_UNUSED(playMode)
}
void Sonos::setPlayModes()
void Sonos::groupSetShuffle(const QByteArray &groupId, bool shuffle)
{
Q_UNUSED(groupId)
Q_UNUSED(shuffle)
}
void Sonos::skipToNextTrack()
void Sonos::groupSetRepeat(const QByteArray &groupId, RepeatMode repeatMode)
{
Q_UNUSED(groupId)
Q_UNUSED(repeatMode)
}
void Sonos::skipToPreviousTrack()
void Sonos::groupSetCrossfade(const QByteArray &groupId, bool crossfade)
{
Q_UNUSED(groupId)
Q_UNUSED(crossfade)
}
void Sonos::togglePlayPause()
void Sonos::groupSkipToNextTrack(const QByteArray &groupId)
{
Q_UNUSED(groupId)
}
void Sonos::getPlayerVolume(int playerId)
void Sonos::groupSkipToPreviousTrack(const QByteArray &groupId)
{
Q_UNUSED(playerId)
Q_UNUSED(groupId)
}
void Sonos::setPlayerVolume(int playerId, int volume)
void Sonos::groupTogglePlayPause(const QByteArray &groupId)
{
Q_UNUSED(playerId)
Q_UNUSED(volume)
Q_UNUSED(groupId)
}
void Sonos::setPlayerRelativeVolume(int playerId, int volumeDelta)
void Sonos::getPlayerVolume(const QByteArray &playerId)
{
Q_UNUSED(playerId)
Q_UNUSED(volumeDelta)
Q_UNUSED(playerId)
}
void Sonos::setPlayerMute(int playerId, bool mute)
void Sonos::setPlayerVolume(const QByteArray &playerId, int volume)
{
Q_UNUSED(playerId)
Q_UNUSED(mute)
Q_UNUSED(playerId)
Q_UNUSED(volume)
}
void Sonos::setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta)
{
Q_UNUSED(playerId)
Q_UNUSED(volumeDelta)
}
void Sonos::setPlayerMute(const QByteArray &playerId, bool mute)
{
Q_UNUSED(playerId)
Q_UNUSED(mute)
}
void Sonos::getPlaylist()

View File

@ -33,19 +33,38 @@ class Sonos : public QObject
Q_OBJECT
public:
enum PlayMode {
Repeat,
RepeatOne,
Shuffle,
Crossfade
enum RepeatMode {
RepeatModeOne,
RepeatModeAll,
RepeatModeNone
};
struct PlayMode {
bool repeat;
bool repeatOne;
bool shuffle;
bool crossfade;
};
struct PlayerObject {
};
/* Represents a Sonos household.*/
struct GroupObject {
QByteArray CoordinatorId; //Player acting as the group coordinator for the group
QByteArray groupId; //The ID of the group.
QString playbackState; //The playback state corresponding to the group.
QList<QByteArray> playerIds; //The IDs of the primary players in the group.
QString displayName; //The display name for the group, such as “Living Room” or “Kitchen + 2”.
};
/*
* Represents a Sonos household.*/
struct HouseholdObject {
/*struct HouseholdObject {
QString id; //Identifies a Sonos household.
QString name; //A user-displayable name of the Sonos household
};
};*/
/*
* The music service identifier or a pseudo-service identifier in the case of local library. */
@ -160,36 +179,40 @@ public:
void getFavorites();
void loadFavorite();
void getGroups();
void createGroup();
void getGroups(const QByteArray &householdId);
void createGroup(const QByteArray &householdId, QList<QByteArray> playerIds);
void modifyGroupMembers();
void setGroupMembers();
void setGroupMembers(const QByteArray &groupId);
//group volume
void getGroupVolume(int groupId); //Get the volume and mute state of a group.
void setGroupVolume(int groupId, int volume); //Set group volume to a specific level and unmute the group if muted.
void setGroupMute(int groupId, bool mute); //Mute and unmute the group.
void setGroupRelativeVolume(int groupId, int volumeDelta); //Increase or decrease group volume.
void getGroupVolume(const QByteArray &groupId); //Get the volume and mute state of a group.
void setGroupVolume(const QByteArray &groupId, int volume); //Set group volume to a specific level and unmute the group if muted.
void setGroupMute(const QByteArray &groupId, bool mute); //Mute and unmute the group.
void setGroupRelativeVolume(const QByteArray &groupId, int volumeDelta); //Increase or decrease group volume.
//playback
void getPlaybackStatus();
void loadLineIn();
void play();
void pause();
void seek();
void seekRelative();
void setPlayModes();
void skipToNextTrack();
void skipToPreviousTrack();
void togglePlayPause();
//group playback
void getGroupPlaybackStatus(const QByteArray &groupId);
void groupLoadLineIn(const QByteArray &groupId);
void groupPlay(const QByteArray &groupId);
void groupPause(const QByteArray &groupId);
void groupSeek(const QByteArray &groupId);
void groupSeekRelative(const QByteArray &groupId, int deltaMillis);
void groupSetPlayModes(const QByteArray &groupId, PlayMode playMode);
void groupSetShuffle(const QByteArray &groupId, bool shuffle);
void groupSetRepeat(const QByteArray &groupId, RepeatMode repeatMode);
void groupSetCrossfade(const QByteArray &groupId, bool crossfade);
void groupSkipToNextTrack(const QByteArray &groupId);
void groupSkipToPreviousTrack(const QByteArray &groupId);
void groupTogglePlayPause(const QByteArray &groupId);
//playbackMetadata
// playerVolume
void getPlayerVolume(int playerId);
void setPlayerVolume(int playerId, int volume);
void setPlayerRelativeVolume(int playerId, int volumeDelta);
void setPlayerMute(int playerId, bool mute);
void getPlayerVolume(const QByteArray &playerId);
void setPlayerVolume(const QByteArray &playerId, int volume);
void setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta);
void setPlayerMute(const QByteArray &playerId, bool mute);
//Playlists API namespace
void getPlaylist();
@ -201,16 +224,18 @@ public:
void setPlayerSettings();
private:
QByteArray m_baseAuthorizationUrl = "api.sonos.com/login/v3/oauth";
QByteArray m_baseControlUrl = "api.ws.sonos.com/control/api/v1";
QByteArray m_baseAuthorizationUrl = "https://api.sonos.com/login/v3/oauth";
QByteArray m_baseControlUrl = "https://api.ws.sonos.com/control/api/v1";
QByteArray m_accessToken;
QByteArray m_apiKey = "b15cbf8c-a39c-47aa-bd93-635a96e9696c";
NetworkAccessManager *m_networkManager = nullptr;
private slots:
signals:
void householdIdsReceived(QList<QByteArray> householdIds);
void groupsReceived(QList<GroupObject> groups);
};