controls work, status missing

master
nymea 2019-07-10 23:12:50 +02:00
parent 3206f285ba
commit f7284bd57e
4 changed files with 606 additions and 0 deletions

318
bose/soundtouch.cpp Normal file
View File

@ -0,0 +1,318 @@
#include "soundtouch.h"
#include "hardwaremanager.h"
#include "devices/device.h"
#include "network/networkaccessmanager.h"
SoundTouch::SoundTouch(NetworkAccessManager *networkAccessManager, QString ipAddress, QObject *parent) :
QObject(parent),
m_networkAccessManager(networkAccessManager),
m_ipAddress(ipAddress)
{
m_websocket = new QWebSocket();
connect(m_websocket, &QWebSocket::connected, this, &SoundTouch::onWebsocketConnected);
connect(m_websocket, &QWebSocket::disconnected, this, &SoundTouch::onWebsocketDisconnected);
connect(m_websocket, &QWebSocket::textMessageReceived, this, &SoundTouch::onWebsocketMessageReceived);
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("ws");
url.setPort(8080);
qDebug(dcBose) << "Connecting websocket to" << url;
//TODO missing websocket subprotocol "gabbo"
m_websocket->open(url);
}
void SoundTouch::getInfo()
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/info");
qDebug(dcBose) << "Sending request" << url;
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
void SoundTouch::getVolume()
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/volume");
qDebug(dcBose) << "Sending request" << url;
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
void SoundTouch::getNowPlaying()
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/now_playing");
qDebug(dcBose) << "Sending request" << url;
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
void SoundTouch::getBass()
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/bass");
qDebug(dcBose) << "Sending request" << url;
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
void SoundTouch::getGroup()
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/group");
qDebug(dcBose) << "Sending request" << url;
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
void SoundTouch::getSources()
{
}
void SoundTouch::getZone()
{
}
void SoundTouch::setKey(KEY_VALUE keyValue)
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/key");
QByteArray content = ("<?xml version=\"1.0\" ?>");
content.append("<key state=\"press\" sender=\"Gabbo\">");
switch (keyValue){
case KEY_VALUE_PLAY:
content.append("PLAY");
break;
case KEY_VALUE_STOP:
content.append("STOP");
break;
case KEY_VALUE_PLAY_PAUSE:
content.append("PLAY_PAUSE");
break;
case KEY_VALUE_POWER:
content.append("POWER");
break;
case KEY_VALUE_NEXT_TRACK:
content.append("NEXT_TRACK");
break;
case KEY_VALUE_PREV_TRACK:
content.append("PREV_TRACK");
break;
case KEY_VALUE_BOOKMARK:
content.append("BOOKMARK");
break;
case KEY_VALUE_AUX_INPUT:
content.append("AUX_INPUT");
break;
case KEY_VALUE_REPEAT_ALL:
content.append("REPEAT_ALL");
break;
case KEY_VALUE_REPEAT_ONE:
content.append("REPEAT_ONE");
break;
case KEY_VALUE_REPEAT_OFF:
content.append("REPEAT_OFF");
break;
case KEY_VALUE_ADD_FAVORITE:
content.append("ADD_FAVORITE");
break;
case KEY_VALUE_MUTE:
content.append("MUTE");
break;
case KEY_VALUE_SHUFFLE_ON:
content.append("SHUFFLE_ON");
break;
case KEY_VALUE_SHUFFLE_OFF:
content.append("SHUFFLE_OFF");
break;
default:
qWarning(dcBose) << "key not yet implemented";
return;
}
content.append("</key>");
qDebug(dcBose) << "Sending request" << url << content;
QNetworkReply *reply = m_networkAccessManager->post(QNetworkRequest(url), content);
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
if (keyValue == KEY_VALUE_POWER) {
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/key");
QByteArray content = ("<?xml version=\"1.0\" ?>");
content.append("<key state=\"release\" sender=\"Gabbo\">");
content.append("POWER");
content.append("</key>");
qDebug(dcBose) << "Sending request" << url << content;
QNetworkReply *reply = m_networkAccessManager->post(QNetworkRequest(url), content);
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
}
void SoundTouch::setVolume(int volume)
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/volume");
QByteArray content = ("<?xml version=\"1.0\" ?>");
content.append("<volume>");
content.append(QByteArray::number(volume));
content.append("</volume>");
qDebug(dcBose) << "Sending request" << url << content;
QNetworkReply *reply = m_networkAccessManager->post(QNetworkRequest(url), content);
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
void SoundTouch::setName(QString name)
{
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("http");
url.setPort(m_port);
url.setPath("/name");
QByteArray content = ("<?xml version=\"1.0\" ?>");
content.append("<name>");
content.append(name);
content.append("</name>");
qDebug(dcBose) << "Sending request" << url << content;
QNetworkReply *reply = m_networkAccessManager->post(QNetworkRequest(url), content);
connect(reply, &QNetworkReply::finished, this, &SoundTouch::onRestRequestFinished);
}
void SoundTouch::onWebsocketConnected()
{
qDebug(dcBose) << "Bose websocket connected";
emit connectionChanged(true);
}
void SoundTouch::onWebsocketDisconnected()
{
qDebug(dcBose) << "Bose websocket disconnected";
emit connectionChanged(false);
QTimer::singleShot(5000, [this](){
QUrl url;
url.setHost(m_ipAddress);
url.setScheme("ws");
url.setPort(8080);
m_websocket->open(url);
});
}
void SoundTouch::onRestRequestFinished() {
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
QByteArray data = reply->readAll();
qDebug(dcBose) << "REST message received:" << data;
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcBose()) << "Request error:" << status << reply->errorString();
}
QXmlStreamReader xml;
xml.addData(data);
if (xml.readNextStartElement()) {
if (xml.name() == "info") {
InfoObject info;
qDebug(dcBose) << "Info Request";
if(xml.attributes().hasAttribute("deviceID")) {
qDebug(dcBose) << "Device ID" << xml.attributes().value("deviceID").toString();
info.deviceID = xml.attributes().value("deviceID").toString();
}
while(xml.readNextStartElement()){
if(xml.name() == "name"){
qDebug(dcBose) << "name" << xml.readElementText();
info.name = xml.readElementText();
} else if(xml.name() == "type"){
qDebug(dcBose) << "type" << xml.readElementText();
info.type = xml.readElementText();
} else if(xml.name() == "components"){
qDebug(dcBose) << "components element";
while(xml.readNextStartElement()){
if(xml.name() == "component"){
while(xml.readNextStartElement()){
if(xml.name() == "softwareVersion"){
qDebug(dcBose) << "Software version" << xml.readElementText();
} else if(xml.name() == "serialNumber") {
qDebug(dcBose) << "Serialnumber" << xml.readElementText();
} else {
xml.skipCurrentElement();
}
}
} else {
xml.skipCurrentElement();
}
}
} else if(xml.name() == "networkInfo"){
qDebug(dcBose) << "network Info";
while (xml.readNextStartElement()) {
if (xml.name() == "macAddress") {
qDebug(dcBose) << "macAddress" << xml.readElementText();
} else if(xml.name() == "ipAddress") {
qDebug(dcBose) << "ipAddress" << xml.readElementText();
} else {
xml.skipCurrentElement();
}
}
} else {
xml.skipCurrentElement();
}
}
emit infoReceived(info);
} else if (xml.name() == "now_playing") {
NowPlayingObject nowPlaying;
emit
}
}
/*while (!xml.atEnd()) {
if (xml.readNext()) {
if (xml.tokenType() == TokenType::)
qDebug(dcBose) << "element" << xml.tokenString() << xml.readElementText();
}
if (xml.hasError()) {
qCWarning(dcBose()) << "Error when parsing XML response";
}
}*/
//}
}
void SoundTouch::onWebsocketMessageReceived(QString message)
{
qDebug(dcBose) << "Websocket message received:" << message;
}

67
bose/soundtouch.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef SOUNDTOUCH_H
#define SOUNDTOUCH_H
#include <QObject>
#include <QXmlStreamReader>
#include <QtWebSockets/QtWebSockets>
#include "extern-plugininfo.h"
#include "soundtouchtypes.h"
#include "hardwaremanager.h"
#include "network/networkaccessmanager.h"
class SoundTouch : public QObject
{
Q_OBJECT
public:
explicit SoundTouch(NetworkAccessManager *networkAccessManager, QString ipAddress, QObject *parent = nullptr);
void getInfo(); //Get basic information about a product.
void getVolume(); //Get the current volume and mute status of a product.
void getNowPlaying(); //Get information about what's playing on a product.
void getBass(); //Get the bass level of a product, if supported.
void getGroup(); //Get the current left/right stereo pair configuration of a product.
void getSources(); //Get available sources for a product.
void getZone(); //Get the current multiroom zone state of a product.
//void getPresets(); //Get information related to the user's SoundTouch presets.
//void getBassCapabilities(); //Get whether a product supports reducing bass.
//void speaker(); //initiate playback of a specified network-accessible audio file on a Bose SoundTouch product.
void setKey(KEY_VALUE keyValue); //Start and control playback on a product.
void setVolume(int volume); //Set the volume of a product.
void setSource(SOURCE_STATUS source); //Select a product's source.
/*void setZone(QString zone); //Create a zone of synced products.
void addZoneSlave(); //Add one or more slave product(s) to a multiroom zone.
void removeZoneSlave(); //Remove one or more slave product(s) from a multiroom zone.
void setBass(int volume); //Set the bass level of a product, if supported.*/
void setName(QString name); //Set the products user-facing name.
private:
NetworkAccessManager *m_networkAccessManager = nullptr;
QString m_ipAddress;
int m_port = 8090;
QWebSocket *m_websocket = nullptr;
//QHash<QNetworkReply *, Device*> m_requests;
signals:
void connectionChanged(bool connected);
void infoReceived(InfoObject info);
void nowPlayingReceived(NowPlayingObject nowPlaying);
void volumeReceived(VolumeObject volume);
void sourcesReceived(SourcesObject sources);
void zoneReceived(ZoneObject);
void bassCapabilitiesReceived(BassCapabilitiesObject bassCapabilities);
void bassReceived(BassObject bass);
void presetsReceived(PresetObject presets);
void groupReceived(GroupObject group);
private slots:
void onWebsocketConnected();
void onWebsocketDisconnected();
void onWebsocketMessageReceived(QString message);
void onRestRequestFinished();
};
#endif // SOUNDTOUCH_H

220
bose/soundtouchtypes.h Normal file
View File

@ -0,0 +1,220 @@
#ifndef SOUNDTOUCHTYPES_H
#define SOUNDTOUCHTYPES_H
#include "extern-plugininfo.h"
enum SHUFFLE_STATUS {
SHUFFLE_STATUS_SHUFFLE_OFF,
SHUFFLE_STATUS_SHUFFLE_ON
};
enum REPEAT_STATUS { //The state of repeat.
REPEAT_STATUS_REPEAT_OFF,
REPEAT_STATUS_REPEAT_ALL,
REPEAT_STATUS_REPEAT_ONE
};
enum STREAM_STATUS { //The type of music container that is streaming.
STREAM_STATUS_TRACK_ONDEMAND,
STREAM_STATUS_RADIO_STREAMING,
STREAM_STATUS_RADIO_TRACKS,
STREAM_STATUS_NO_TRANSPORT_CONTROLS
};
enum SOURCE_STATUS { //The availability of an audio source
SOURCE_STATUS_UNAVAILABLE,
SOURCE_STATUS_READY
};
enum PLAY_STATUS { //The state of the audio stream
PLAY_STATUS_PLAY_STATE,
PLAY_STATUS_PAUSE_STATE,
PLAY_STATUS_STOP_STATE,
PLAY_STATUS_BUFFERING_STATE
};
enum ART_STATUS { //The state of an image
ART_STATUS_INVALID,
ART_STATUS_SHOW_DEFAULT_IMAGE,
ART_STATUS_DOWNLOADING,
ART_STATUS_IMAGE_PRESENT
};
enum KEY_VALUE { //An enumeration of virtual device buttons that may be pressed
KEY_VALUE_PLAY,
KEY_VALUE_PAUSE,
KEY_VALUE_PLAY_PAUSE,
KEY_VALUE_STOP,
KEY_VALUE_PREV_TRACK,
KEY_VALUE_NEXT_TRACK,
KEY_VALUE_POWER,
KEY_VALUE_MUTE,
KEY_VALUE_AUX_INPUT,
KEY_VALUE_SHUFFLE_ON,
KEY_VALUE_SHUFFLE_OFF,
KEY_VALUE_REPEAT_ONE,
KEY_VALUE_REPEAT_ALL,
KEY_VALUE_REPEAT_OFF,
KEY_VALUE_ADD_FAVORITE,
KEY_VALUE_REMOVE_FAVORITE,
KEY_VALUE_THUMBS_UP,
KEY_VALUE_THUMBS_DOWN,
KEY_VALUE_BOOKMARK,
KEY_VALUE_PRESET_1,
KEY_VALUE_PRESET_2,
KEY_VALUE_PRESET_3,
KEY_VALUE_PRESET_4,
KEY_VALUE_PRESET_5,
KEY_VALUE_PRESET_6
};
enum KEY_STATE { //The state of the virtual key press
KEY_STATE_PRESS,
KEY_STATE_RELEASE
};
enum GROUP_LOC {
GROUP_LOC_LEFT,
GROUP_LOC_RIGHT
};
struct ComponentObject {
QString softwareVersion; //Element. The firmware version of the component.
QString serialNumber; //Element. The serial number of the component.
};
struct NetworkInfoObject {
QString macAddress; //Element. The MAC Address of the component.
QString ipAddress; //Element. The IP Address of the product.
};
struct InfoObject {
QString deviceID; //Attribute. Unique identifier of the product.
QString name; //Element. The user-set product name. You can change this with /name.
QString type; //Element. The Bose-defined name for the product. Ex: SoundTouch 10.
QList<ComponentObject> components; //Element. A container for all component objects. See component object.
NetworkInfoObject networkInfo; //Element. This object describes the product's current connection information. See networkInfo object.
};
struct ArtObject {
ART_STATUS artStatus;
QString url;
};
struct TimeObject {
int total; //Attribute. Tells you the current time the track has played, in seconds.
int length; //Content. The length of the track, in seconds.
};
struct ContentItemObject {
QString source; //Attribute. The type or name of the service playing. To determine if the product is in standby mode, check if source == STANDBY.
QString location; //Attribute. READ-ONLY. This attribute is used by Bose to point to the content the user is accessing. You can save it to access content later, but do not attempt to generate your own locations.
QString sourceAccount; //Attribute. The user-account associated with this source.
bool isPresetable; //Attribute. TRUE if the source can be set as one of the six (6) SoundTouch presets.
QString itemName; //Element. The album, station, playlist, song, phone, etc. name depending on the source.
QString containerArt; //URL
};
struct ConnectionStatusInfoObject {
QString status; //Attribute. The connection status.
QString deviceName; //Attribute. The name of the Bluetooth source.
};
struct NowPlayingObject {
QString deviceID; //Attribute. Unique identifier of the product.
QString source; //Attribute. The type or name of the service playing. To determine if the product is in standby mode, check if source == STANDBY.
QString sourceAccount; //Attribute. The account associated with this source.
ContentItemObject ContentItem; //Element. This object describes the content container that is playing on the product. You should treat this object as an opaque blob, for use in /select.
QString track; //Element. The track name.
QString artist; //Eement. The artist name.
QString album; //Element. The album name.
QString genre; //Element. The music genre.
QString rating; //Element. The user rating of the song.
QString stationName; //Element. The station or playlist name.
ArtObject art; //Element. This object provides information for source content art. See art object
TimeObject time; //Element. This object provides information for current and total track time. See time object
bool skipEnabled; //Element. If tag is present, the /key value NEXT_TRACK is valid.
bool skipPreviousEnabled; //Element. If tag is present, the /key value PREV_TRACK is valid.
bool favoriteEnabled; //Element. If tag is present, the /key values ADD_FAVORITE and REMOVE_FAVORITE are valid.
bool isFavorit; //Element. If tag is present, the track or station has been favorited by the user.
bool rateEnabled; //Element. If tag is present, the track can be rated by the user using /key THUMBS_UP and THUMBS_DOWN.
//QString rating; //Element. UP, DOWN, or NONE rating. If tag isn't present, the track has not been rated by the user.
PLAY_STATUS playStatus; //Element. This indicates whether the audio product is currently playing, paused, in standby, or in some other state.
SHUFFLE_STATUS shuffleSetting; //Element. The shuffle state. If this tag is present, the /key values SHUFFLE_ON and SHUFFLE_OFF are valid.
REPEAT_STATUS repeatSettings; //Element. The repeat state. If this tag is present, the /key values REPEAT_OFF, REPEAT_ONE, and REPEAT_ALL are valid.
STREAM_STATUS streamType; //Element. The type of music that is streaming.
QString stationLocation; //Element. The location of the source ex: "Internet Only".
ConnectionStatusInfoObject ConnectionStatusInfo; //Element. This object describes the connection status to the Bluetooth source. See ConnectionStatusInfo object.
};
struct SourceItemObject {
QString source; //Attribute. The name of the source.
QString sourceAccount; //Attribute. The user account associated with the source.
SOURCE_STATUS status; //Attribute. Indicates whether the source is available.
bool isLocal; //Attribute. TRUE if a local source (AUX or BLUETOOTH)
bool multiroomallowed; //Attribute. TRUE if the source can be rebroadcast in a multi-room zone.
QString displayName; //Content. The user-facing name of the source.
};
struct SourcesObject {
QString deviceId;
SourceItemObject sourceItem;
};
struct VolumeObject {
QString deviceID; //Attribute. Unique identifier of the product.
int targetVolume; //Element. The volume the product is trying to reach, 0 to 100 inclusive. Bigger is louder.
int actualVolume; //Element. The current product volume, 0 to 100 inclusive. Bigger is louder.
bool muteEnabled; //Element. TRUE if the product is muted.
};
struct MemberObject {
QString ipAddress; //Attribute. The IP address of the product.
QString deviceID; //The deviceID unique identifier of the product.
};
struct ZoneObject {
QString deviceID; //Attribute. The deviceID unique identifier of the master product.
MemberObject member; //Element. This object describes products in the zone There is an object for each product.
};
struct BassCapabilitiesObject {
QString deviceID; //Attribute. Unique identifier of the product.
bool bassAvailable; //Element. TRUE if the bass of the advice is adjustable.
int bassMin; //Element. The minimum value the bass can be set to.
int bassMax; //Element. The maximum value the bass can be set to.
int bassDefault; //Element. The default bass value.
};
struct BassObject {
QString deviceID; //Attribute. Unique identifier of the product.
int targetBass; //Element. The bass level the product is trying to reach.
int actualBass; //Element. The current bass level.
};
struct PresetObject {
int presetId; //Attribute. The number of the SoundTouch preset, 1-6 inclusive.
uint64_t createdOn; //Attribute. A timestamp of when the preset was originally created.
uint64_t updatedOn; //Attribute. A timestamp of the last time the preset was changed.
ContentItemObject ContentItem; //Attribute. An object describing the source. See /now_playing for full documentation on this object.
};
struct GroupRoleObject {
QString deviceID; //Unique identifier of the product.
GROUP_LOC role; //The user-set location of the product. LEFT or RIGHT.
QString ipAddress; //The IP Address of the product.
};
struct RolesObject {
GroupRoleObject groupRole; //This object describes a product in the group.
};
struct GroupObject {
QString id; //Attribute. A unique ID for the group.
QString name; //Element. A user-set name for the group.
QString masterDeviceId; //Element. The unique identifier of the master product in the group.
RolesObject roles; //Element. This object describes the products in the group and their location (left/right).
PLAY_STATUS status; //Element. The state of the stereo pair group.
};
#endif // SOUNDTOUCHTYPES_H

View File

@ -5,6 +5,7 @@ PLUGIN_DIRS = \
avahimonitor \
awattar \
boblight \
bose \
commandlauncher \
conrad \
datetime \