discovery and basic control works nor
parent
6a199f5ae8
commit
c2aa46d6e3
481
bluos/bluos.cpp
481
bluos/bluos.cpp
|
|
@ -0,0 +1,481 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "bluos.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrlQuery>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
BluOS::BluOS(NetworkAccessManager *networkmanager, QHostAddress hostAddress, int port, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_hostAddress(hostAddress),
|
||||
m_port(port),
|
||||
m_networkManager(networkmanager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int BluOS::port()
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
|
||||
QHostAddress BluOS::hostAddress()
|
||||
{
|
||||
return m_hostAddress;
|
||||
}
|
||||
|
||||
QUuid BluOS::getStatus()
|
||||
{
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/Status");
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
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) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
QXmlStreamReader xml;
|
||||
xml.addData(reply->readAll());
|
||||
if (xml.hasError()) {
|
||||
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
|
||||
}
|
||||
|
||||
StatusResponse statusResponse;
|
||||
if (xml.readNextStartElement()) {
|
||||
if (xml.name() == "status") {
|
||||
while(xml.readNextStartElement()){
|
||||
if(xml.name() == "artist"){
|
||||
} else if(xml.name() == "artist"){
|
||||
statusResponse.Artist = xml.readElementText();
|
||||
} else if(xml.name() == "album"){
|
||||
statusResponse.Album = xml.readElementText();
|
||||
} else if(xml.name() == "name"){
|
||||
statusResponse.Name = xml.readElementText();
|
||||
} else if(xml.name() == "service"){
|
||||
statusResponse.Service = xml.readElementText();
|
||||
} else if(xml.name() == "serviceIcon"){
|
||||
statusResponse.ServiceIcon = xml.readElementText();
|
||||
} else if(xml.name() == "shuffle"){
|
||||
statusResponse.Shuffle = xml.readElementText().toInt();
|
||||
} else if(xml.name() == "repeat"){
|
||||
statusResponse.Shuffle = xml.readElementText().toInt();
|
||||
} else if(xml.name() == "state"){
|
||||
statusResponse.PlaybackState = xml.readElementText().toInt();
|
||||
} else if(xml.name() == "volume"){
|
||||
statusResponse.Volume = xml.readElementText().toInt();
|
||||
} else if(xml.name() == "mute"){
|
||||
statusResponse.Mute = xml.readElementText().toInt();
|
||||
} else {
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emit statusReceived(statusResponse);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::setVolume(uint volume)
|
||||
{
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("level", QString::number(volume));
|
||||
query.addQueryItem("tell_slaves", "off");
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/Volume");
|
||||
url.setQuery(query);
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
||||
reply->deleteLater();
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
emit actionExecuted(requestId, false);
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
|
||||
QXmlStreamReader xml;
|
||||
xml.addData(reply->readAll());
|
||||
if (xml.hasError()) {
|
||||
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
|
||||
}
|
||||
int volume = 0;
|
||||
bool mute = false;
|
||||
if (xml.readNextStartElement()) {
|
||||
if (xml.name() == "volume") {
|
||||
if(xml.attributes().hasAttribute("mute")) {
|
||||
mute = xml.attributes().value("mute").toInt();
|
||||
}
|
||||
volume = xml.readElementText().toInt();
|
||||
}
|
||||
}
|
||||
emit volumeReceived(volume, mute);
|
||||
emit actionExecuted(requestId, true);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::setMute(bool mute)
|
||||
{
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("mute", QString::number(mute));
|
||||
query.addQueryItem("tell_slaves", "off");
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/Volume");
|
||||
url.setQuery(query);
|
||||
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
||||
reply->deleteLater();
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
emit actionExecuted(requestId, false);
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
emit actionExecuted(requestId, true);
|
||||
});
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::play()
|
||||
{
|
||||
return playBackControl(PlaybackCommand::Play);
|
||||
}
|
||||
|
||||
QUuid BluOS::pause()
|
||||
{
|
||||
return playBackControl(PlaybackCommand::Pause);
|
||||
}
|
||||
|
||||
QUuid BluOS::stop()
|
||||
{
|
||||
return playBackControl(PlaybackCommand::Stop);
|
||||
}
|
||||
|
||||
QUuid BluOS::back()
|
||||
{
|
||||
return playBackControl(PlaybackCommand::Back);
|
||||
}
|
||||
|
||||
QUuid BluOS::setShuffle(bool shuffle)
|
||||
{
|
||||
Q_UNUSED(shuffle)
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/Shuffle");
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("state", QString::number(shuffle));
|
||||
url.setQuery(query);
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
||||
reply->deleteLater();
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
emit actionExecuted(requestId, false);
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
emit actionExecuted(requestId, true);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::setRepeat(RepeatMode repeatMode)
|
||||
{
|
||||
Q_UNUSED(repeatMode)
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/Repeat");
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("state", QString::number(repeatMode));
|
||||
url.setQuery(query);
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
||||
reply->deleteLater();
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
|
||||
}
|
||||
emit actionExecuted(requestId, false);
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
emit actionExecuted(requestId, true);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::listPresets()
|
||||
{
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/Presets");
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
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) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
});
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::loadPreset(int preset)
|
||||
{
|
||||
Q_UNUSED(preset)
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/Presets");
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("id", QString::number(preset));
|
||||
url.setQuery(query);
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
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) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::addGroupPlayer(QHostAddress address, int port)
|
||||
{
|
||||
Q_UNUSED(address)
|
||||
Q_UNUSED(port)
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/AddSlave");
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("slave", address.toString());
|
||||
query.addQueryItem("port", QString::number(port));
|
||||
url.setQuery(query);
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
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) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::removeGroupPlayer(QHostAddress address, int port)
|
||||
{
|
||||
Q_UNUSED(address)
|
||||
Q_UNUSED(port)
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
url.setPath("/RemoveSlave");
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("slave", address.toString());
|
||||
query.addQueryItem("port", QString::number(port));
|
||||
url.setQuery(query);
|
||||
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
|
||||
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) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
QUuid BluOS::skip()
|
||||
{
|
||||
return playBackControl(PlaybackCommand::Skip);
|
||||
}
|
||||
|
||||
QUuid BluOS::playBackControl(BluOS::PlaybackCommand command)
|
||||
{
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(m_hostAddress.toString());
|
||||
url.setPort(m_port);
|
||||
switch (command) {
|
||||
case PlaybackCommand::Play:
|
||||
url.setPath("/Play");
|
||||
break;
|
||||
case PlaybackCommand::Pause:
|
||||
url.setPath("/Pause");
|
||||
break;
|
||||
case PlaybackCommand::Stop:
|
||||
url.setPath("/Stop");
|
||||
break;
|
||||
case PlaybackCommand::Back:
|
||||
url.setPath("/Back");
|
||||
break;
|
||||
case PlaybackCommand::Skip:
|
||||
url.setPath("/Skip");
|
||||
break;
|
||||
}
|
||||
QNetworkRequest request;
|
||||
request.setUrl(url);
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
qCDebug(dcBluOS()) << "Sending request" << request.url();
|
||||
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
||||
reply->deleteLater();
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||
emit connectionChanged(false);
|
||||
}
|
||||
emit actionExecuted(requestId, false);
|
||||
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
|
||||
return;
|
||||
}
|
||||
emit connectionChanged(true);
|
||||
|
||||
QXmlStreamReader xml;
|
||||
xml.addData(reply->readAll());
|
||||
if (xml.hasError()) {
|
||||
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
|
||||
}
|
||||
emit actionExecuted(requestId, true);
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
127
bluos/bluos.h
127
bluos/bluos.h
|
|
@ -0,0 +1,127 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef BLUOS_H
|
||||
#define BLUOS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QHostAddress>
|
||||
#include <QUuid>
|
||||
|
||||
#include "network/networkaccessmanager.h"
|
||||
#include "integrations/thing.h"
|
||||
|
||||
class BluOS : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum PlaybackCommand {
|
||||
Play,
|
||||
Pause,
|
||||
Stop,
|
||||
Skip,
|
||||
Back
|
||||
};
|
||||
|
||||
enum RepeatMode {
|
||||
All,
|
||||
One,
|
||||
None
|
||||
};
|
||||
|
||||
struct StatusResponse {
|
||||
QString Album;
|
||||
QString Artist;
|
||||
QString Name;
|
||||
QString Service;
|
||||
QUrl ServiceIcon;
|
||||
QString PlaybackState;
|
||||
QUrl StationUrl;
|
||||
int Volume;
|
||||
bool Mute;
|
||||
RepeatMode Repeat;
|
||||
bool Shuffle;
|
||||
};
|
||||
|
||||
struct Preset {
|
||||
int Prid;
|
||||
QString name;
|
||||
int Id;
|
||||
QString url;
|
||||
};
|
||||
|
||||
explicit BluOS(NetworkAccessManager *networkManager, QHostAddress hostAddress, int port, QObject *parent = nullptr);
|
||||
int port();
|
||||
QHostAddress hostAddress();
|
||||
|
||||
// Status Queries
|
||||
QUuid getStatus();
|
||||
|
||||
// Volume Control
|
||||
QUuid setVolume(uint volume);
|
||||
QUuid setMute(bool mute);
|
||||
|
||||
// Playback Control
|
||||
QUuid play();
|
||||
QUuid pause();
|
||||
QUuid stop();
|
||||
QUuid skip();
|
||||
QUuid back();
|
||||
QUuid setShuffle(bool shuffle);
|
||||
QUuid setRepeat(RepeatMode repeatMode);
|
||||
|
||||
// Presets
|
||||
QUuid listPresets();
|
||||
QUuid loadPreset(int preset); //1 for next preset, -1 for previous preset
|
||||
|
||||
// Content Browsing
|
||||
|
||||
// Player Grouping
|
||||
QUuid addGroupPlayer(QHostAddress address, int port); //adds player as slave
|
||||
QUuid removeGroupPlayer(QHostAddress address, int port);
|
||||
|
||||
private:
|
||||
QHostAddress m_hostAddress;
|
||||
int m_port;
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
|
||||
QUuid playBackControl(PlaybackCommand command);
|
||||
|
||||
signals:
|
||||
void connectionChanged(bool connected);
|
||||
void actionExecuted(QUuid actionId, bool success);
|
||||
|
||||
void presetsReceived(const QList<Preset> &presets);
|
||||
void statusReceived(const StatusResponse &status);
|
||||
void volumeReceived(int volume, bool mute);
|
||||
};
|
||||
#endif // BLUOS_H
|
||||
|
|
@ -33,8 +33,7 @@
|
|||
#include "plugininfo.h"
|
||||
#include "integrations/thing.h"
|
||||
#include "network/networkaccessmanager.h"
|
||||
#include "platform/platformzeroconfcontroller.h"
|
||||
#include "network/zeroconf/zeroconfservicebrowser.h"
|
||||
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
|
|
@ -54,119 +53,242 @@ IntegrationPluginBluOS::~IntegrationPluginBluOS()
|
|||
|
||||
void IntegrationPluginBluOS::init()
|
||||
{
|
||||
|
||||
m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_musc._tcp");
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
QTimer::singleShot(5000, info, [this, info](){
|
||||
foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) {
|
||||
qCDebug(dcBluOS()) << "Zeroconf entry:" << avahiEntry;
|
||||
|
||||
if (info->thingClassId() == bluosThingClassId) {
|
||||
/*
|
||||
* The HEOS products can be discovered using the UPnP SSDP protocol. Through discovery,
|
||||
* the IP address of the HEOS products can be retrieved. Once the IP address is retrieved,
|
||||
* a telnet connection to port 1255 can be opened to access the HEOS CLI and control the HEOS system.
|
||||
* The HEOS product IP address can also be set statically and manually programmed into the control system.
|
||||
* Search target name (ST) in M-SEARCH discovery request is 'urn:schemas-denon-com:thing:ACT-Denon:1'.
|
||||
*/
|
||||
UpnpDiscoveryReply *reply = hardwareManager()->upnpDiscovery()->discoverDevices();
|
||||
connect(reply, &UpnpDiscoveryReply::finished, info, [this, reply, info](){
|
||||
reply->deleteLater();
|
||||
QString playerId = avahiEntry.hostName().split(".").first();
|
||||
ThingDescriptor descriptor(bluosPlayerThingClassId, avahiEntry.name(), avahiEntry.hostAddress().toString());
|
||||
ParamList params;
|
||||
|
||||
if (reply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) {
|
||||
qCWarning(dcDenon()) << "Upnp discovery error" << reply->error();
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("UPnP discovery failed."));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const UpnpDeviceDescriptor &upnpDevice, reply->deviceDescriptors()) {
|
||||
qCDebug(dcDenon) << "UPnP thing found:" << upnpDevice.modelDescription() << upnpDevice.friendlyName() << upnpDevice.hostAddress().toString() << upnpDevice.modelName() << upnpDevice.manufacturer() << upnpDevice.serialNumber();
|
||||
|
||||
if (upnpDevice.modelName().contains("HEOS")) {
|
||||
QString serialNumber = upnpDevice.serialNumber();
|
||||
if (serialNumber != "0000001") {
|
||||
// child devices have serial number 0000001
|
||||
qCDebug(dcDenon) << "UPnP thing found:" << upnpDevice.modelDescription() << upnpDevice.friendlyName() << upnpDevice.hostAddress().toString() << upnpDevice.modelName() << upnpDevice.manufacturer() << upnpDevice.serialNumber();
|
||||
ThingDescriptor descriptor(heosThingClassId, upnpDevice.modelName(), serialNumber);
|
||||
ParamList params;
|
||||
foreach (Thing *existingThing, myThings()) {
|
||||
if (existingThing->paramValue(heosThingSerialNumberParamTypeId).toString().contains(serialNumber, Qt::CaseSensitivity::CaseInsensitive)) {
|
||||
descriptor.setThingId(existingThing->id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
params.append(Param(heosThingModelNameParamTypeId, upnpDevice.modelName()));
|
||||
params.append(Param(heosThingIpParamTypeId, upnpDevice.hostAddress().toString()));
|
||||
params.append(Param(heosThingSerialNumberParamTypeId, serialNumber));
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
foreach (Thing *existingDevice, myThings().filterByThingClassId(bluosPlayerThingClassId)) {
|
||||
if (existingDevice->paramValue(bluosPlayerThingSerialNumberParamTypeId).toString() == playerId) {
|
||||
descriptor.setThingId(existingDevice->id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
return;
|
||||
}
|
||||
info->finish(Thing::ThingErrorThingClassNotFound);
|
||||
params << Param(bluosPlayerThingAddressParamTypeId, avahiEntry.hostAddress().toString());
|
||||
params << Param(bluosPlayerThingPortParamTypeId, avahiEntry.port());
|
||||
params << Param(bluosPlayerThingSerialNumberParamTypeId, playerId);
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginDenon::setupThing(ThingSetupInfo *info)
|
||||
void IntegrationPluginBluOS::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
if (thing->thingClassId() == AVRX1000ThingClassId) {
|
||||
qCDebug(dcDenon) << "Setup Denon device" << thing->paramValue(AVRX1000ThingIpParamTypeId).toString();
|
||||
if (thing->thingClassId() == bluosPlayerThingClassId) {
|
||||
qCDebug(dcBluOS()) << "Setup BluOS device" << thing->paramValue(bluosPlayerThingAddressParamTypeId).toString();
|
||||
|
||||
QHostAddress address(thing->paramValue(AVRX1000ThingIpParamTypeId).toString());
|
||||
if (address.isNull()) {
|
||||
qCWarning(dcDenon) << "Could not parse ip address" << thing->paramValue(AVRX1000ThingIpParamTypeId).toString();
|
||||
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The given IP address is not valid."));
|
||||
return;
|
||||
}
|
||||
QHostAddress address(thing->paramValue(bluosPlayerThingAddressParamTypeId).toString());
|
||||
int port = thing->paramValue(bluosPlayerThingPortParamTypeId).toInt();
|
||||
BluOS *bluos = new BluOS(hardwareManager()->networkManager() , address, port, this);
|
||||
connect(bluos, &BluOS::connectionChanged, this, &IntegrationPluginBluOS::onConnectionChanged);
|
||||
connect(bluos, &BluOS::statusReceived, this, &IntegrationPluginBluOS::onStatusResponseReceived);
|
||||
connect(bluos, &BluOS::actionExecuted, this, &IntegrationPluginBluOS::onActionExecuted);
|
||||
|
||||
AvrConnection *denonConnection = new AvrConnection(address, 23, this);
|
||||
connect(denonConnection, &AvrConnection::connectionStatusChanged, this, &IntegrationPluginDenon::onAvrConnectionChanged);
|
||||
connect(denonConnection, &AvrConnection::socketErrorOccured, this, &IntegrationPluginDenon::onAvrSocketError);
|
||||
connect(denonConnection, &AvrConnection::channelChanged, this, &IntegrationPluginDenon::onAvrChannelChanged);
|
||||
connect(denonConnection, &AvrConnection::powerChanged, this, &IntegrationPluginDenon::onAvrPowerChanged);
|
||||
connect(denonConnection, &AvrConnection::volumeChanged, this, &IntegrationPluginDenon::onAvrVolumeChanged);
|
||||
connect(denonConnection, &AvrConnection::surroundModeChanged, this, &IntegrationPluginDenon::onAvrSurroundModeChanged);
|
||||
connect(denonConnection, &AvrConnection::muteChanged, this, &IntegrationPluginDenon::onAvrMuteChanged);
|
||||
|
||||
m_avrConnections.insert(thing, denonConnection);
|
||||
m_asyncAvrSetups.insert(denonConnection, info);
|
||||
m_asyncSetup.insert(bluos, info);
|
||||
bluos->getStatus();
|
||||
// In case the setup is cancelled before we finish it...
|
||||
connect(info, &QObject::destroyed, this, [this, info, denonConnection]() { m_asyncAvrSetups.remove(denonConnection); });
|
||||
connect(info, &QObject::destroyed, this, [this, bluos]() {
|
||||
m_asyncSetup.remove(bluos);
|
||||
|
||||
denonConnection->connectDevice();
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
return info->finish(Thing::ThingErrorThingClassNotFound);
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == heosThingClassId) {
|
||||
qCDebug(dcDenon) << "Setup Denon device" << thing->paramValue(heosThingIpParamTypeId).toString();
|
||||
|
||||
QHostAddress address(thing->paramValue(heosThingIpParamTypeId).toString());
|
||||
Heos *heos = new Heos(address, this);
|
||||
connect(heos, &Heos::connectionStatusChanged, this, &IntegrationPluginDenon::onHeosConnectionChanged);
|
||||
connect(heos, &Heos::playerDiscovered, this, &IntegrationPluginDenon::onHeosPlayerDiscovered);
|
||||
connect(heos, &Heos::playStateReceived, this, &IntegrationPluginDenon::onHeosPlayStateReceived);
|
||||
connect(heos, &Heos::repeatModeReceived, this, &IntegrationPluginDenon::onHeosRepeatModeReceived);
|
||||
connect(heos, &Heos::shuffleModeReceived, this, &IntegrationPluginDenon::onHeosShuffleModeReceived);
|
||||
connect(heos, &Heos::muteStatusReceived, this, &IntegrationPluginDenon::onHeosMuteStatusReceived);
|
||||
connect(heos, &Heos::volumeStatusReceived, this, &IntegrationPluginDenon::onHeosVolumeStatusReceived);
|
||||
connect(heos, &Heos::nowPlayingMediaStatusReceived, this, &IntegrationPluginDenon::onHeosNowPlayingMediaStatusReceived);
|
||||
|
||||
m_heos.insert(thing, heos);
|
||||
m_asyncHeosSetups.insert(heos, info);
|
||||
// In case the setup is cancelled before we finish it...
|
||||
connect(info, &QObject::destroyed, this, [this, info, heos]() { m_asyncHeosSetups.remove(heos); });
|
||||
|
||||
heos->connectHeos();
|
||||
return;
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == heosPlayerThingClassId) {
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
return;
|
||||
}
|
||||
info->finish(Thing::ThingErrorThingClassNotFound);
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::postSetupThing(Thing *thing)
|
||||
{
|
||||
Q_UNUSED(thing);
|
||||
|
||||
if (!m_pluginTimer) {
|
||||
//m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
|
||||
//connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginBluOS::onPluginTimer);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == bluosPlayerThingClassId) {
|
||||
BluOS *bluos = m_bluos.take(thing->id());
|
||||
bluos->deleteLater();
|
||||
} else {
|
||||
qCWarning(dcBluOS()) << "Things removed, unhandled thing class id";
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
Action action = info->action();
|
||||
|
||||
if (thing->thingClassId() == bluosPlayerThingClassId) {
|
||||
BluOS *bluos = m_bluos.value(thing->id());
|
||||
if (!bluos) {
|
||||
return info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
if (action.actionTypeId() == bluosPlayerPlaybackStatusActionTypeId) {
|
||||
QString playbakStatus = action.param(bluosPlayerPlaybackStatusEventPlaybackStatusParamTypeId).value().toString();
|
||||
QUuid requestId;
|
||||
if (playbakStatus == "Playing") {
|
||||
requestId = bluos->play();
|
||||
} else if (playbakStatus == "Paused") {
|
||||
requestId = bluos->pause();
|
||||
} else if (playbakStatus == "Stopped") {
|
||||
requestId = bluos->stop();
|
||||
}
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerPlayActionTypeId) {
|
||||
QUuid requestId = bluos->play();
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerPauseActionTypeId) {
|
||||
QUuid requestId = bluos->pause();
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerStopActionTypeId) {
|
||||
QUuid requestId = bluos->stop();
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerSkipNextActionTypeId) {
|
||||
QUuid requestId = bluos->skip();
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerSkipBackActionTypeId) {
|
||||
QUuid requestId = bluos->back();
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerVolumeActionTypeId) {
|
||||
uint volume = action.param(bluosPlayerVolumeActionVolumeParamTypeId).value().toUInt();
|
||||
QUuid requestId = bluos->setVolume(volume);
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerMuteActionTypeId) {
|
||||
bool mute = action.param(bluosPlayerMuteActionMuteParamTypeId).value().toBool();
|
||||
QUuid requestId = bluos->setMute(mute);
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerShuffleActionTypeId) {
|
||||
bool shuffle = action.param(bluosPlayerShuffleActionShuffleParamTypeId).value().toBool();
|
||||
QUuid requestId = bluos->setMute(shuffle);
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else if (action.actionTypeId() == bluosPlayerRepeatActionTypeId) {
|
||||
QString repeat = action.param(bluosPlayerRepeatActionRepeatParamTypeId).value().toString();
|
||||
QUuid requestId;
|
||||
if (repeat == "One") {
|
||||
requestId = bluos->setRepeat(BluOS::RepeatMode::One);
|
||||
} else if (repeat == "All") {
|
||||
requestId = bluos->setRepeat(BluOS::RepeatMode::All);
|
||||
} else if (repeat == "None") {
|
||||
requestId = bluos->setRepeat(BluOS::RepeatMode::None);
|
||||
}
|
||||
m_asyncActions.insert(requestId, info);
|
||||
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
|
||||
} else {
|
||||
qCWarning(dcBluOS()) << "Execute Action, unhandled action type id" << action.actionTypeId();
|
||||
return info->finish(Thing::ThingErrorThingClassNotFound);
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcBluOS()) << "Execute Action, unhandled thing class id" << thing->thingClassId();
|
||||
return info->finish(Thing::ThingErrorThingClassNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::browseThing(BrowseResult *result)
|
||||
{
|
||||
Q_UNUSED(result)
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::browserItem(BrowserItemResult *result)
|
||||
{
|
||||
Q_UNUSED(result)
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::executeBrowserItem(BrowserActionInfo *info)
|
||||
{
|
||||
Q_UNUSED(info)
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::onConnectionChanged(bool connected)
|
||||
{
|
||||
BluOS *bluos = static_cast<BluOS*>(sender());
|
||||
|
||||
if (m_asyncSetup.contains(bluos)) {
|
||||
ThingSetupInfo *info = m_asyncSetup.take(bluos);
|
||||
if (connected) {
|
||||
m_bluos.insert(info->thing()->id(), bluos);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
}
|
||||
} else {
|
||||
Thing *thing = myThings().findById(m_bluos.key(bluos));
|
||||
if (!thing)
|
||||
return;
|
||||
thing->setStateValue(bluosPlayerConnectedStateTypeId, connected);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::onStatusResponseReceived(const BluOS::StatusResponse &status)
|
||||
{
|
||||
BluOS *bluos = static_cast<BluOS*>(sender());
|
||||
Thing *thing = myThings().findById(m_bluos.key(bluos));
|
||||
if (!thing)
|
||||
return;
|
||||
thing->setStateValue(bluosPlayerArtistStateTypeId, status.Artist);
|
||||
thing->setStateValue(bluosPlayerCollectionStateTypeId, status.Album);
|
||||
thing->setStateValue(bluosPlayerTitleStateTypeId, status.Name);
|
||||
thing->setStateValue(bluosPlayerSourceStateTypeId, status.Service);
|
||||
thing->setStateValue(bluosPlayerArtworkStateTypeId, status.ServiceIcon);
|
||||
thing->setStateValue(bluosPlayerPlaybackStatusStateTypeId, status.PlaybackState);
|
||||
thing->setStateValue(bluosPlayerMuteStateTypeId, status.Mute);
|
||||
thing->setStateValue(bluosPlayerVolumeStateTypeId, status.Volume);
|
||||
thing->setStateValue(bluosPlayerShuffleStateTypeId, status.Shuffle);
|
||||
switch (status.Repeat) {
|
||||
case BluOS::RepeatMode::All:
|
||||
thing->setStateValue(bluosPlayerRepeatStateTypeId, "All");
|
||||
break;
|
||||
case BluOS::RepeatMode::One:
|
||||
thing->setStateValue(bluosPlayerRepeatStateTypeId, "One");
|
||||
break;
|
||||
case BluOS::RepeatMode::None:
|
||||
thing->setStateValue(bluosPlayerRepeatStateTypeId, "None");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::onActionExecuted(QUuid requestId, bool success)
|
||||
{
|
||||
if (m_asyncActions.contains(requestId)) {
|
||||
ThingActionInfo *info = m_asyncActions.take(requestId);
|
||||
if (success) {
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginBluOS::onVolumeReceived(int volume, bool mute)
|
||||
{
|
||||
BluOS *bluos = static_cast<BluOS*>(sender());
|
||||
Thing *thing = myThings().findById(m_bluos.key(bluos));
|
||||
if (!thing)
|
||||
return;
|
||||
thing->setStateValue(bluosPlayerMuteStateTypeId, mute);
|
||||
thing->setStateValue(bluosPlayerVolumeStateTypeId, volume);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,13 @@
|
|||
#ifndef INTEGRATIONPLUGINBLUOS_H
|
||||
#define INTEGRATIONPLUGINBLUOS_H
|
||||
|
||||
#include "bluos.h"
|
||||
|
||||
#include "integrations/integrationplugin.h"
|
||||
#include "platform/platformzeroconfcontroller.h"
|
||||
#include "network/zeroconf/zeroconfservicebrowser.h"
|
||||
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class PluginTimer;
|
||||
|
|
@ -53,17 +56,32 @@ public:
|
|||
void init() override;
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
|
||||
void browseThing(BrowseResult *result) override;
|
||||
void browserItem(BrowserItemResult *result) override;
|
||||
void executeBrowserItem(BrowserActionInfo *info) override;
|
||||
|
||||
private:
|
||||
PluginTimer *m_pluginTimer = nullptr;
|
||||
ZeroConfServiceBrowser *m_serviceBrowser = nullptr;
|
||||
|
||||
QHash<ThingId, BluOS *> m_bluos;
|
||||
QHash<BluOS *, ThingSetupInfo *> m_asyncSetup;
|
||||
QHash<QUuid, ThingActionInfo *> m_asyncActions;
|
||||
QHash<QUuid, BrowseResult *> m_asyncBrowseResults;
|
||||
QHash<QUuid, BrowserActionInfo *> m_asyncExecuteBrowseItems;
|
||||
QHash<QUuid, BrowserItemResult *> m_asyncBrowseItemResults;
|
||||
|
||||
private slots:
|
||||
void refreshStates();
|
||||
|
||||
private:
|
||||
|
||||
private:
|
||||
PluginTimer *m_pollTimer = nullptr;
|
||||
void onPluginTimer();
|
||||
|
||||
void onConnectionChanged(bool connected);
|
||||
void onStatusResponseReceived(const BluOS::StatusResponse &status);
|
||||
void onActionExecuted(QUuid actionId, bool success);
|
||||
void onVolumeReceived(int volume, bool mute);
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINBLUOS_H
|
||||
|
|
|
|||
|
|
@ -1,40 +1,35 @@
|
|||
{
|
||||
"displayName": "BluOS",
|
||||
"name": "bluos",
|
||||
"id": "",
|
||||
"name": "BluOS",
|
||||
"id": "71dd25b3-37ef-4b27-abca-24989fa38c61",
|
||||
"vendors": [
|
||||
{
|
||||
"id": "cf0a9644-2c13-4daf-85c1-ad88d6745b42",
|
||||
"displayName": "Denon",
|
||||
"name": "denon",
|
||||
"id": "39a492e9-e497-4b43-94d4-970eb9913b96",
|
||||
"displayName": "BluOS",
|
||||
"name": "bluos",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "fce5247f-4c6d-408f-ac62-e5973dc6adfa",
|
||||
"name": "heosPlayer",
|
||||
"displayName": "Heos player",
|
||||
"createMethods": ["auto"],
|
||||
"interfaces": ["mediaplayer", "mediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"],
|
||||
"id": "406adcbc-1e7d-4e41-ae2a-f87b6bafd13d",
|
||||
"name": "bluosPlayer",
|
||||
"displayName": "BluOS player",
|
||||
"createMethods": ["discovery"],
|
||||
"interfaces": ["extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"],
|
||||
"browsable": true,
|
||||
"paramTypes":[
|
||||
{
|
||||
"id": "89629008-6ad8-4e92-863d-b86e0e012d0b",
|
||||
"name": "playerId",
|
||||
"displayName": "Player ID",
|
||||
"id": "833f99cc-fc3f-48ef-a705-a69ae2c8e9ec",
|
||||
"name": "address",
|
||||
"displayName": "IP Address",
|
||||
"type" : "QString"
|
||||
},
|
||||
{
|
||||
"id": "4628c040-6bbb-43c9-b25f-ce3b22300e3b",
|
||||
"name": "port",
|
||||
"displayName": "Port",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"id": "e760f92b-8fca-4f20-aead-a52045505b81",
|
||||
"name": "model",
|
||||
"displayName": "Model",
|
||||
"type" : "QString"
|
||||
},
|
||||
{
|
||||
"id": "aa1158f7-b451-456a-840f-4f0c63b2b7f0",
|
||||
"name": "version",
|
||||
"displayName": "Version",
|
||||
"type" : "QString"
|
||||
},
|
||||
{
|
||||
"id": "866e8d6a-953f-4bdc-8d85-8d92e51e8592",
|
||||
"id": "8fd2a7d5-bb26-4f18-a488-5ab0779733f8",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type" : "QString"
|
||||
|
|
@ -42,7 +37,7 @@
|
|||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "9a4e527e-057c-4b19-8a02-605cc8349f5e",
|
||||
"id": "8092f387-5099-43e8-a7c4-7f0dd7b70fce",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected changed",
|
||||
|
|
@ -51,7 +46,7 @@
|
|||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "fcc89c7c-b793-4b6f-a3dc-0e0e3a86748f",
|
||||
"id": "2f650ae6-f3a5-4851-8449-1ba02c4864e5",
|
||||
"name": "mute",
|
||||
"displayName": "Mute",
|
||||
"displayNameEvent": "Mute changed",
|
||||
|
|
@ -62,7 +57,7 @@
|
|||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "6d4886a1-fa5d-4889-96c5-7a1c206f59be",
|
||||
"id": "a444bb7c-7266-4c7e-874a-eb04bb91b9cf",
|
||||
"name": "volume",
|
||||
"displayName": "Volume",
|
||||
"displayNameEvent": "Volume changed",
|
||||
|
|
@ -74,7 +69,7 @@
|
|||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "6db3b484-4cd4-477b-b822-275865d308db",
|
||||
"id": "43f552c7-7dfe-4358-bebd-ab558191cdfc",
|
||||
"name": "playbackStatus",
|
||||
"displayName": "Playback status",
|
||||
"displayNameEvent": "Playback status changed",
|
||||
|
|
@ -86,7 +81,7 @@
|
|||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "4b581237-acf5-4d8f-9e83-9b24e9ac900a",
|
||||
"id": "5636c21a-f14b-472c-8486-177543f6adfb",
|
||||
"name": "shuffle",
|
||||
"displayName": "Shuffle",
|
||||
"displayNameEvent": "Shuffle changed",
|
||||
|
|
@ -97,7 +92,7 @@
|
|||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "4e60cd17-5845-4351-aa2c-2504610e1532",
|
||||
"id": "b4a572b1-6120-43e8-9a6c-18d099c8b162",
|
||||
"name": "repeat",
|
||||
"displayName": "Repeat mode",
|
||||
"displayNameEvent": "Repeat mode changed",
|
||||
|
|
@ -109,7 +104,7 @@
|
|||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "eee22722-3ee5-48f7-8af8-275dc04b21eb",
|
||||
"id": "604e4995-ee1a-44fc-bfcb-ca0e861710bd",
|
||||
"name": "source",
|
||||
"displayName": "Source",
|
||||
"displayNameEvent": "Source changed",
|
||||
|
|
@ -117,7 +112,7 @@
|
|||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "0a9183a4-b633-4773-ba7a-f4266895157e",
|
||||
"id": "8c5372f1-3dde-4984-9955-9a436a29e8e3",
|
||||
"name": "artist",
|
||||
"displayName": "Artist",
|
||||
"displayNameEvent": "Artist changed",
|
||||
|
|
@ -125,7 +120,7 @@
|
|||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "9cd60864-f141-4e03-a85b-357690cad1b8",
|
||||
"id": "d2a2db24-5855-40cd-a043-6f67e43acc61",
|
||||
"name": "collection",
|
||||
"displayName": "Album",
|
||||
"displayNameEvent": "Album changed",
|
||||
|
|
@ -133,7 +128,7 @@
|
|||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "bbeecf30-6feb-48d5-ade3-57b2a4eea05f",
|
||||
"id": "6204ec9d-6aab-4fd3-8911-fdaa462c19a8",
|
||||
"name": "title",
|
||||
"displayName": "Title",
|
||||
"displayNameEvent": "Title changed",
|
||||
|
|
@ -141,49 +136,37 @@
|
|||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "a7f0ba95-383a-4efd-adc5-a36e50a04018",
|
||||
"id": "918bfeed-bf96-4034-85d6-9f4cff35fd00",
|
||||
"name": "artwork",
|
||||
"displayName": "Artwork",
|
||||
"displayNameEvent": "Artwork changed",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "c59835ac-ee6e-4e6c-aa20-aeb3501937c5",
|
||||
"name": "playerType",
|
||||
"displayName": "Player type",
|
||||
"displayNameEvent": "Player type changed",
|
||||
"possibleValues": [
|
||||
"audio",
|
||||
"video"
|
||||
],
|
||||
"type": "QString",
|
||||
"defaultValue": "audio"
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "a718f7e9-0b54-4403-b661-49f7b0d13085",
|
||||
"id": "864a5bcc-71e1-4d7f-8f2e-3c4222d5d988",
|
||||
"name": "skipBack",
|
||||
"displayName": "Akip back"
|
||||
"displayName": "Skip back"
|
||||
},
|
||||
{
|
||||
"id": "c4b29c09-e3b3-4843-b6d9-e032f3fc1d78",
|
||||
"id": "2526ec6d-1c21-4f73-97f1-973a5f05b626",
|
||||
"name": "stop",
|
||||
"displayName": "Stop"
|
||||
},
|
||||
{
|
||||
"id": "c64964e4-cea0-468a-a9bf-8f69657b74e9",
|
||||
"id": "253eb62f-d50d-4667-8213-8632de178aa3",
|
||||
"name": "play",
|
||||
"displayName": "Play"
|
||||
},
|
||||
{
|
||||
"id": "21c1cbe6-278f-4688-a65f-6620be1ee5ea",
|
||||
"id": "92446566-8c32-4ee2-9498-d9dd9333a75d",
|
||||
"name": "pause",
|
||||
"displayName": "Pause"
|
||||
},
|
||||
{
|
||||
"id": "57697e9c-ce5e-4b8f-b42e-16662829ceb2",
|
||||
"id": "30930095-6f97-48ef-8bbf-597d734f0751",
|
||||
"name": "skipNext",
|
||||
"displayName": "Skip next"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue