fixed heos rediscovery

This commit is contained in:
nymea 2019-07-12 18:14:19 +02:00
parent 4db6b6bddb
commit 0eb0933b34
7 changed files with 560 additions and 203 deletions

View File

@ -14,4 +14,5 @@ HEADERS += \
deviceplugindenon.h \
heos.h \
heosplayer.h \
avrconnection.h
avrconnection.h \
heostypes.h

View File

@ -81,7 +81,6 @@ Device::DeviceError DevicePluginDenon::discoverDevices(const DeviceClassId &devi
qCDebug(dcDenon) << "service discovered" << name << "ID:" << id;
if (discoveredIds.contains(id))
break;
discoveredIds.append(id);
DeviceDescriptor deviceDescriptor(AVRX1000DeviceClassId, name, address);
ParamList params;
@ -142,7 +141,7 @@ Device::DeviceSetupStatus DevicePluginDenon::setupDevice(Device *device)
connect(denonConnection, &AvrConnection::surroundModeChanged, this, &DevicePluginDenon::onAvrSurroundModeChanged);
connect(denonConnection, &AvrConnection::muteChanged, this, &DevicePluginDenon::onAvrMuteChanged);
m_asyncSetups.append(denonConnection);
m_asyncAvrSetups.append(denonConnection);
denonConnection->connectDevice();
m_avrConnections.insert(device, denonConnection);
return Device::DeviceSetupStatusAsync;
@ -162,6 +161,7 @@ Device::DeviceSetupStatus DevicePluginDenon::setupDevice(Device *device)
connect(heos, &Heos::volumeStatusReceived, this, &DevicePluginDenon::onHeosVolumeStatusReceived);
connect(heos, &Heos::nowPlayingMediaStatusReceived, this, &DevicePluginDenon::onHeosNowPlayingMediaStatusReceived);
m_asyncHeosSetups.append(heos);
heos->connectHeos();
m_heos.insert(device, heos);
return Device::DeviceSetupStatusAsync;
@ -263,21 +263,24 @@ Device::DeviceError DevicePluginDenon::executeAction(Device *device, const Actio
if (action.actionTypeId() == heosPlayerPlaybackStatusActionTypeId) {
QString playbackStatus = action.param(heosPlayerPlaybackStatusActionPlaybackStatusParamTypeId).value().toString();
if (playbackStatus == "playing") {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Play);
heos->setPlayerState(playerId, PLAYER_STATE_PLAY);
} else if (playbackStatus == "stopping") {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Stop);
heos->setPlayerState(playerId, PLAYER_STATE_STOP);
} else if (playbackStatus == "pausing") {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Pause);
heos->setPlayerState(playerId, PLAYER_STATE_PAUSE);
}
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerShuffleActionTypeId) {
bool shuffle = action.param(heosPlayerShuffleActionShuffleParamTypeId).value().toBool();
Heos::HeosRepeatMode repeatMode;
repeatMode = Heos::HeosRepeatMode::Off;
REPEAT_MODE repeatMode = REPEAT_MODE_OFF;
if ( device->stateValue(heosPlayerRepeatStateTypeId) == "One") {
repeatMode = REPEAT_MODE_ONE;
} else if ( device->stateValue(heosPlayerRepeatStateTypeId) == "All") {
repeatMode = REPEAT_MODE_ALL;
}
heos->setPlayMode(playerId, repeatMode, shuffle);
return Device::DeviceErrorNoError;
}
@ -292,18 +295,17 @@ Device::DeviceError DevicePluginDenon::executeAction(Device *device, const Actio
}
if (action.actionTypeId() == heosPlayerStopActionTypeId) {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Stop);
heos->setPlayerState(playerId, PLAYER_STATE_STOP);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPlayActionTypeId) {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Play);
heos->setPlayerState(playerId, PLAYER_STATE_PLAY);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPauseActionTypeId) {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Pause);
heos->setPlayerState(playerId, PLAYER_STATE_PAUSE);
return Device::DeviceErrorNoError;
}
@ -327,11 +329,10 @@ void DevicePluginDenon::postSetupDevice(Device *device)
Heos *heos = m_heos.value(device);
heos->getPlayers();
device->setStateValue(heosConnectedStateTypeId, heos->connected());
}
if (device->deviceClassId() == heosPlayerDeviceClassId) {
device->setStateValue(heosPlayerConnectedStateTypeId, true);
Device *heosDevice = myDevices().findById(device->parentId());
Heos *heos = m_heos.value(heosDevice);
int playerId = device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt();
@ -340,7 +341,6 @@ void DevicePluginDenon::postSetupDevice(Device *device)
heos->getVolume(playerId);
heos->getMute(playerId);
heos->getNowPlayingMedia(playerId);
device->setStateValue(heosPlayerConnectedStateTypeId, heos->connected());
}
}
@ -361,10 +361,6 @@ void DevicePluginDenon::onPluginTimer()
if (device->deviceClassId() == heosDeviceClassId) {
Heos *heos = m_heos.value(device);
if (!heos->connected()) {
heos->connectHeos();
}
device->setStateValue(heosConnectedStateTypeId, heos->connected());
heos->getPlayers();
heos->registerForChangeEvents(true);
}
@ -394,8 +390,8 @@ void DevicePluginDenon::onAvrConnectionChanged(bool status)
// if the device is connected
if (status) {
// and from the first setup
if (m_asyncSetups.contains(denonConnection)) {
m_asyncSetups.removeAll(denonConnection);
if (m_asyncAvrSetups.contains(denonConnection)) {
m_asyncAvrSetups.removeAll(denonConnection);
emit deviceSetupFinished(device, Device::DeviceSetupStatusSuccess);
}
@ -475,8 +471,8 @@ void DevicePluginDenon::onAvrSocketError()
if (device->deviceClassId() == AVRX1000DeviceClassId) {
// Check if setup running for this device
if (m_asyncSetups.contains(denonConnection)) {
m_asyncSetups.removeAll(denonConnection);
if (m_asyncAvrSetups.contains(denonConnection)) {
m_asyncAvrSetups.removeAll(denonConnection);
qCWarning(dcDenon()) << "Could not add device. The setup failed.";
emit deviceSetupFinished(device, Device::DeviceSetupStatusFailure);
// Delete the connection, the device will not be added and
@ -502,7 +498,6 @@ void DevicePluginDenon::onUpnpDiscoveryFinished()
}
QList<DeviceDescriptor> heosDescriptors;
QList<DeviceDescriptor> avrDescriptors;
foreach (const UpnpDeviceDescriptor &upnpDevice, reply->deviceDescriptors()) {
if (upnpDevice.modelName().contains("HEOS")) {
@ -513,7 +508,7 @@ void DevicePluginDenon::onUpnpDiscoveryFinished()
DeviceDescriptor descriptor(heosDeviceClassId, upnpDevice.modelName(), serialNumber);
ParamList params;
foreach (Device *existingDevice, myDevices()) {
if (existingDevice->paramValue(heosDeviceSerialNumberParamTypeId).toString() == serialNumber) {
if (existingDevice->paramValue(heosDeviceSerialNumberParamTypeId).toString().contains(serialNumber, Qt::CaseSensitivity::CaseInsensitive)) {
descriptor.setDeviceId(existingDevice->id());
break;
}
@ -525,22 +520,40 @@ void DevicePluginDenon::onUpnpDiscoveryFinished()
heosDescriptors.append(descriptor);
}
}
//if (upnpDevice.modelName().contains("")) {
qCDebug(dcDenon) << "UPnP device found:" << upnpDevice.modelDescription() << upnpDevice.friendlyName() << upnpDevice.hostAddress().toString() << upnpDevice.modelName() << upnpDevice.manufacturer() << upnpDevice.serialNumber();
//}
}
if (!heosDescriptors.isEmpty()) {
emit devicesDiscovered(heosDeviceClassId, heosDescriptors);
}
}
void DevicePluginDenon::onHeosConnectionChanged()
void DevicePluginDenon::onHeosConnectionChanged(bool status)
{
Heos *heos = static_cast<Heos *>(sender());
heos->registerForChangeEvents(true);
Device *device = m_heos.key(heos);
if (!device->setupComplete() && heos->connected()) {
emit deviceSetupFinished(device, Device::DeviceSetupStatusSuccess);
if (!device)
return;
if (device->deviceClassId() == heosDeviceClassId) {
// if the device is connected
if (status) {
// and from the first setup
if (m_asyncHeosSetups.contains(heos)) {
m_asyncHeosSetups.removeAll(heos);
heos->getPlayers();
emit deviceSetupFinished(device, Device::DeviceSetupStatusSuccess);
}
}
device->setStateValue(heosConnectedStateTypeId, status);
// update connection status for all child devices
foreach (Device *playerDevice, myDevices()) {
if (playerDevice->deviceClassId() == heosPlayerDeviceClassId) {
if (playerDevice->parentId() == device->id()) {
playerDevice->setStateValue(heosPlayerConnectedStateTypeId, status);
}
}
}
}
}
@ -568,14 +581,14 @@ void DevicePluginDenon::onHeosPlayerDiscovered(HeosPlayer *heosPlayer) {
autoDevicesAppeared(heosPlayerDeviceClassId, heosPlayerDescriptors);
}
void DevicePluginDenon::onHeosPlayStateReceived(int playerId, Heos::HeosPlayerState state)
void DevicePluginDenon::onHeosPlayStateReceived(int playerId, PLAYER_STATE state)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
if (state == Heos::HeosPlayerState::Pause) {
if (state == PLAYER_STATE_PAUSE) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Paused");
} else if (state == Heos::HeosPlayerState::Play) {
} else if (state == PLAYER_STATE_PLAY) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Playing");
} else if (state == Heos::HeosPlayerState::Stop) {
} else if (state == PLAYER_STATE_STOP) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Stopped");
}
break;
@ -583,14 +596,14 @@ void DevicePluginDenon::onHeosPlayStateReceived(int playerId, Heos::HeosPlayerSt
}
void DevicePluginDenon::onHeosRepeatModeReceived(int playerId, Heos::HeosRepeatMode repeatMode)
void DevicePluginDenon::onHeosRepeatModeReceived(int playerId, REPEAT_MODE repeatMode)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
if (repeatMode == Heos::HeosRepeatMode::All) {
if (repeatMode == REPEAT_MODE_ALL) {
device->setStateValue(heosPlayerRepeatStateTypeId, "All");
} else if (repeatMode == Heos::HeosRepeatMode::One) {
} else if (repeatMode == REPEAT_MODE_ONE) {
device->setStateValue(heosPlayerRepeatStateTypeId, "One");
} else if (repeatMode == Heos::HeosRepeatMode::Off) {
} else if (repeatMode == REPEAT_MODE_OFF) {
device->setStateValue(heosPlayerRepeatStateTypeId, "None");
}
break;
@ -600,11 +613,7 @@ void DevicePluginDenon::onHeosRepeatModeReceived(int playerId, Heos::HeosRepeatM
void DevicePluginDenon::onHeosShuffleModeReceived(int playerId, bool shuffle)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
if (shuffle) {
device->setStateValue(heosPlayerMuteStateTypeId, true);
} else {
device->setStateValue(heosPlayerMuteStateTypeId, false);
}
device->setStateValue(heosPlayerMuteStateTypeId, shuffle);
break;
}
}
@ -625,13 +634,85 @@ void DevicePluginDenon::onHeosVolumeStatusReceived(int playerId, int volume)
}
}
void DevicePluginDenon::onHeosNowPlayingMediaStatusReceived(int playerId, QString source, QString artist, QString album, QString song, QString artwork)
void DevicePluginDenon::onHeosNowPlayingMediaStatusReceived(int playerId, SOURCE_ID sourceId, QString artist, QString album, QString song, QString artwork)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
device->setStateValue(heosPlayerArtistStateTypeId, artist);
device->setStateValue(heosPlayerTitleStateTypeId, song);
device->setStateValue(heosPlayerArtworkStateTypeId, artwork);
device->setStateValue(heosPlayerCollectionStateTypeId, album);
QString source;
switch (sourceId) {
case SOURCE_ID_PANDORA:
source = "Pandora";
break;
case SOURCE_ID_RHAPSODY:
source = "Rhapsody";
break;
case SOURCE_ID_TUNEIN:
source = "TuneIn";
break;
case SOURCE_ID_SPOTIFY:
source = "Spotify";
break;
case SOURCE_ID_DEEZER:
source = "Deezer";
break;
case SOURCE_ID_NAPSTER:
source = "Napster";
break;
case SOURCE_ID_IHEARTRADIO:
source = "iHeartRadio";
break;
case SOURCE_ID_SIRIUS_XM:
source = "Sirius XM";
break;
case SOURCE_ID_SOUNDCLOUD:
source = "Soundcloud";
break;
case SOURCE_ID_TIDAL:
source = "Tidal";
break;
case SOURCE_ID_FUTURE_SERVICE_1:
source = "Unknown";
break;
case SOURCE_ID_RDIO:
source = "Rdio";
break;
case SOURCE_ID_AMAZON_MUSIC:
source = "Amazon Music";
break;
case SOURCE_ID_FUTURE_SERVICE_2:
source = "Unknown";
break;
case SOURCE_ID_MOODMIX:
source = "Moodmix";
break;
case SOURCE_ID_JUKE:
source = "Juke";
break;
case SOURCE_ID_FUTURE_SERVICE_3:
source = "Unkown";
break;
case SOURCE_ID_QQMUSIC:
source = "QQMusic";
break;
case SOURCE_ID_LOCAL_MEDIA:
source = "USB Media/DLNA Servers";
break;
case SOURCE_ID_HEOS_PLAYLIST:
source = "HEOS Playlists";
break;
case SOURCE_ID_HEOS_HISTORY:
source = "HEOS History";
break;
case SOURCE_ID_HEOS_FAVORITES:
source = "HEOS Favorites";
break;
case SOURCE_ID_HEOS_AUX:
source = "HEOS aux input";
break;
};
device->setStateValue(heosPlayerSourceStateTypeId, source);
break;
}

View File

@ -62,7 +62,8 @@ private:
QHash<Device *, AvrConnection*> m_avrConnections;
QHash<Device *, Heos*> m_heos;
QList<AvrConnection *> m_asyncSetups;
QList<AvrConnection *> m_asyncAvrSetups;
QList<Heos *> m_asyncHeosSetups;
QHash<int, Device *> m_playerIds;
QHash<int, Device *> m_discoveredPlayerIds;
@ -73,14 +74,14 @@ private slots:
void onPluginTimer();
void onUpnpDiscoveryFinished();
void onHeosConnectionChanged();
void onHeosConnectionChanged(bool status);
void onHeosPlayerDiscovered(HeosPlayer *heosPlayer);
void onHeosPlayStateReceived(int playerId, Heos::HeosPlayerState state);
void onHeosPlayStateReceived(int playerId, PLAYER_STATE state);
void onHeosShuffleModeReceived(int playerId, bool shuffle);
void onHeosRepeatModeReceived(int playerId, Heos::HeosRepeatMode repeatMode);
void onHeosRepeatModeReceived(int playerId, REPEAT_MODE repeatMode);
void onHeosMuteStatusReceived(int playerId, bool mute);
void onHeosVolumeStatusReceived(int playerId, int volume);
void onHeosNowPlayingMediaStatusReceived(int playerId, QString source, QString artist, QString album, QString Song, QString artwork);
void onHeosNowPlayingMediaStatusReceived(int playerId, SOURCE_ID source, QString artist, QString album, QString Song, QString artwork);
void onAvahiServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry);
void onAvahiServiceEntryRemoved(const ZeroConfServiceEntry &serviceEntry);

View File

@ -26,6 +26,7 @@
#include <QJsonObject>
#include <QJsonDocument>
#include <QUrlQuery>
#include <QTimer>
Heos::Heos(const QHostAddress &hostAddress, QObject *parent) :
QObject(parent),
@ -39,13 +40,22 @@ Heos::Heos(const QHostAddress &hostAddress, QObject *parent) :
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
}
void Heos::getPlayers()
Heos::~Heos()
{
QByteArray cmd = "heos://player/get_players\r\n";
m_socket->write(cmd);
m_socket->close();
}
void Heos::connectHeos()
{
if (m_socket->state() == QAbstractSocket::ConnectingState) {
return;
}
m_socket->connectToHost(m_hostAddress, 1255);
}
/*
* SYSTEM COMMANDS
*/
void Heos::registerForChangeEvents(bool state)
{
QByteArray query;
@ -66,6 +76,63 @@ void Heos::sendHeartbeat()
m_socket->write(cmd);
}
void Heos::getUserAccount()
{
QByteArray cmd = "heos://system/check_account\r\n";
m_socket->write(cmd);
}
void Heos::setUserAccount(QString userName, QString password)
{
QByteArray cmd = "heos://system/sign_in?un=" + userName.toLocal8Bit() + "&pw=" + password.toLocal8Bit() + "\r\n";
m_socket->write(cmd);
}
void Heos::logoutUserAccount()
{
QByteArray cmd = "heos://system/sign_out\r\n";
m_socket->write(cmd);
}
void Heos::rebootSpeaker()
{
QByteArray cmd = "heos://system/reboot\r\n";
m_socket->write(cmd);
}
void Heos::prettifyJsonResponse(bool enable)
{
QByteArray cmd = "heos://system/prettify_json_response?enable=";
if (enable) {
cmd.append("on=\r\n");
} else {
cmd.append("off=\r\n");
}
m_socket->write(cmd);
}
/*
* PLAYER COMMANDS
*/
void Heos::getNowPlayingMedia(int playerId)
{
QByteArray cmd = "heos://player/get_now_playing_media?pid=" + QVariant(playerId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
HeosPlayer *Heos::getPlayer(int playerId)
{
return m_heosPlayers.value(playerId);
}
void Heos::getPlayers()
{
QByteArray cmd = "heos://player/get_players\r\n";
m_socket->write(cmd);
}
void Heos::getVolume(int playerId)
{
QByteArray cmd = "heos://player/get_volume?pid=" + QVariant(playerId).toByteArray() + "\r\n";
@ -98,15 +165,15 @@ void Heos::setMute(int playerId, bool state)
m_socket->write(cmd);
}
void Heos::setPlayerState(int playerId, HeosPlayerState state)
void Heos::setPlayerState(int playerId, PLAYER_STATE state)
{
QByteArray playerStateQuery;
if (state == HeosPlayerState::Play){
if (state == PLAYER_STATE_PLAY){
playerStateQuery = "&state=play";
} else if (state == HeosPlayerState::Pause){
} else if (state == PLAYER_STATE_PAUSE){
playerStateQuery = "&state=pause";
} else if (state == HeosPlayerState::Stop){
} else if (state == PLAYER_STATE_STOP){
playerStateQuery = "&state=stop";
}
@ -122,15 +189,15 @@ void Heos::getPlayerState(int playerId)
}
void Heos::setPlayMode(int playerId, HeosRepeatMode repeatMode, bool shuffle)
void Heos::setPlayMode(int playerId, REPEAT_MODE repeatMode, bool shuffle)
{
QByteArray repeatModeQuery;
if (repeatMode == HeosRepeatMode::Off) {
if (repeatMode == REPEAT_MODE_OFF) {
repeatModeQuery = "&repeat=off";
} else if (repeatMode == HeosRepeatMode::One) {
} else if (repeatMode == REPEAT_MODE_ONE) {
repeatModeQuery = "&repeat=on_one";
} else if (repeatMode == HeosRepeatMode::All) {
} else if (repeatMode == REPEAT_MODE_ALL) {
repeatModeQuery = "&repeat=on_all";
}
@ -152,6 +219,39 @@ void Heos::getPlayMode(int playerId)
m_socket->write(cmd);
}
void Heos::getQueue(int playerId)
{
QByteArray cmd = "heos://player/get_queue?pid=" + QVariant(playerId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
/*
* GROUP COMMANDS
*/
void Heos::getGroups()
{
QByteArray cmd = "heos://group/get_groups\r\n";
m_socket->write(cmd);
}
void Heos::getGroupInfo(int groupId)
{
QByteArray cmd = "heos://group/get_group_info?gid=" + QVariant(groupId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
void Heos::getGroupVolume(int groupId)
{
QByteArray cmd = "heos://group/get_volume?gid=" + QVariant(groupId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
void Heos::getGroupMute(int groupId)
{
QByteArray cmd = "heos://group/get_mute?gid=" + QVariant(groupId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
void Heos::playNext(int playerId)
{
QByteArray cmd = "heos://player/play_next?pid=" + QVariant(playerId).toByteArray() + "\r\n";
@ -166,45 +266,137 @@ void Heos::playPrevious(int playerId)
m_socket->write(cmd);
}
void Heos::getNowPlayingMedia(int playerId)
void Heos::volumeUp(int playerId, int step)
{
QByteArray cmd = "heos://player/get_now_playing_media?pid=" + QVariant(playerId).toByteArray() + "\r\n";
QByteArray cmd = "heos://player/volume_up?pid=" + QVariant(playerId).toByteArray() + "&step=" + QVariant(step).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Volume up:" << cmd;
m_socket->write(cmd);
}
Heos::~Heos()
void Heos::volumeDown(int playerId, int step)
{
m_socket->close();
QByteArray cmd = "heos://player/volume_down?pid=" + QVariant(playerId).toByteArray() + "&step=" + QVariant(step).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Volume down:" << cmd;
m_socket->write(cmd);
}
bool Heos::connected()
void Heos::clearQueue(int playerId)
{
return m_connected;
QByteArray cmd = "heos://player/clear_queue?pid=" + QVariant(playerId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "clear queue:" << cmd;
m_socket->write(cmd);
}
void Heos::connectHeos()
void Heos::moveQueue(int playerId, int sourcQueueId, int destinationQueueId)
{
if (m_socket->state() == QAbstractSocket::ConnectingState) {
return;
QUrl url("player");
url.setScheme("heos");
url.setPath("move_queue_item");
url.setQuery(QString("pid=%1").arg(playerId));
url.setQuery(QString("sqid=%1").arg(sourcQueueId));
url.setQuery(QString("dqid=%1").arg(destinationQueueId));
qCDebug(dcDenon) << "moving queue:" << url;
m_socket->write(url.toEncoded());
}
void Heos::checkForFirmwareUpdate(int playerId)
{
QByteArray cmd = "heos://player/check_update?pid=" + QVariant(playerId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Check firmware update:" << cmd;
m_socket->write(cmd);
}
void Heos::setGroupVolume(int groupId, bool volume)
{
QByteArray cmd = "heos://group/set_volume?gid=" + QVariant(groupId).toByteArray() + "&level=" + QVariant(volume).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Volume up:" << cmd;
m_socket->write(cmd);
}
void Heos::setGroupMute(int groupId, bool mute)
{
QByteArray cmd = "heos://group/set_mute?gid=" + QVariant(groupId).toByteArray() + "&state=";
if (mute) {
cmd.append("on\r\n");
} else {
cmd.append("off\r\n");
}
m_socket->connectToHost(m_hostAddress, 1255);
m_socket->write(cmd);
}
HeosPlayer *Heos::getPlayer(int playerId)
void Heos::toggleGroupMute(int groupId)
{
return m_heosPlayers.value(playerId);
QByteArray cmd = "heos://group/toggle_mute?gid=" + QVariant(groupId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Volume up:" << cmd;
m_socket->write(cmd);
}
void Heos::groupVolumeUp(int groupId, int step)
{
QByteArray cmd = "heos://group/volume_up?pid=" + QVariant(groupId).toByteArray() + "&step=" + QVariant(step).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Group volume up:" << cmd;
m_socket->write(cmd);
}
void Heos::groupVolumeDown(int groupId, int step)
{
QByteArray cmd = "heos://group/volume_down?pid=" + QVariant(groupId).toByteArray() + "&step=" + QVariant(step).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Group volume up:" << cmd;
m_socket->write(cmd);
}
void Heos::getMusicSources()
{
QByteArray cmd = "heos://browse/get_music_sources\r\n";
m_socket->write(cmd);
}
void Heos::getSourceInfo(SOURCE_ID sourceId)
{
QByteArray cmd = " heos://browse/get_source_info?sid=" + QVariant(sourceId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Group volume up:" << cmd;
m_socket->write(cmd);
}
void Heos::getSearchCriteria(SOURCE_ID sourceId)
{
QByteArray cmd = "heos://browse/get_search_criteria?sid=" + QVariant(sourceId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Group volume up:" << cmd;
m_socket->write(cmd);
}
void Heos::browseSource(SOURCE_ID sourceId)
{
QByteArray cmd = "heos://browse/browse?sid=" + QVariant(sourceId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Group volume up:" << cmd;
m_socket->write(cmd);
}
/* This command is used to perform the following actions:
* Create new group: Creates new group. First player id in the list is group leader.
* Adds or delete players from the group. First player id should be the group leader id.
* Ungroup all players in the group
* Ungroup players. Player id (pid) should be the group leader id.
*/
//void Heos::setGroup()
//{
//}
void Heos::onConnected()
{
qCDebug(dcDenon()) << "connected successfully to" << m_hostAddress.toString();
setConnected(true);
emit connectionStatusChanged(true);
}
void Heos::onDisconnected()
{
qCDebug(dcDenon()) << "disconnected from" << m_hostAddress.toString();
setConnected(false);
qCDebug(dcDenon()) << "Disconnected from" << m_hostAddress.toString() << "try reconnecting in 5 seconds";
QTimer::singleShot(5000, this, [this](){
connectHeos();
});
emit connectionStatusChanged(false);
}
void Heos::onError(QAbstractSocket::SocketError socketError)
@ -214,7 +406,7 @@ void Heos::onError(QAbstractSocket::SocketError socketError)
void Heos::readData()
{
int playerId;
int playerId = 0;
QByteArray data;
QJsonParseError error;
@ -270,92 +462,19 @@ void Heos::readData()
QString song = dataMap.value("payload").toMap().value("song").toString();
QString artwork = dataMap.value("payload").toMap().value("image_url").toString();
QString album = dataMap.value("payload").toMap().value("album").toString();
QString source;
switch (dataMap.value("payload").toMap().value("sid").toInt()) {
case 1:
source = "Pandora";
break;
case 2:
source = "Rhapsody";
break;
case 3:
source = "TuneIn";
break;
case 4:
source = "Spotify";
break;
case 5:
source = "Deezer";
break;
case 6:
source = "Napster";
break;
case 7:
source = "iHeartRadio";
break;
case 8:
source = "Sirius XM";
break;
case 9:
source = "Soundcloud";
break;
case 10:
source = "Tidal";
break;
case 11:
source = "Unknown";
break;
case 12:
source = "Rdio";
break;
case 13:
source = "Amazon Music";
break;
case 14:
source = "Unknown";
break;
case 15:
source = "Moodmix";
break;
case 16:
source = "Juke";
break;
case 17:
source = "Unkown";
break;
case 18:
source = "QQMusic";
break;
case 1024:
source = "USB Media/DLNA Servers";
break;
case 1025:
source = "HEOS Playlists";
break;
case 1026:
source = "HEOS History";
break;
case 1027:
source = "HEOS aux inputs";
break;
case 1028:
source = "HEOS aux inputs";
break;
default:
source = "Unknown";
};
emit nowPlayingMediaStatusReceived(playerId, source, artist, album, song, artwork);
SOURCE_ID sourceId = SOURCE_ID(dataMap.value("payload").toMap().value("sid").toInt());
emit nowPlayingMediaStatusReceived(playerId, sourceId, artist, album, song, artwork);
}
if (command.contains("get_play_state") || command.contains("set_play_state")) {
if (message.hasQueryItem("state")) {
HeosPlayerState playState = HeosPlayerState::Stop;
PLAYER_STATE playState = PLAYER_STATE_STOP;
if (message.queryItemValue("state").contains("play")) {
playState = HeosPlayerState::Play;
playState = PLAYER_STATE_PLAY;
} else if (message.queryItemValue("state").contains("pause")) {
playState = HeosPlayerState::Pause;
playState = PLAYER_STATE_PAUSE;
} else if (message.queryItemValue("state").contains("stop")) {
playState = HeosPlayerState::Stop;
playState = PLAYER_STATE_STOP;
}
emit playStateReceived(playerId, playState);
}
@ -389,13 +508,13 @@ void Heos::readData()
}
emit shuffleModeReceived(playerId, shuffle);
HeosRepeatMode repeatMode = HeosRepeatMode::Off;
REPEAT_MODE repeatMode = REPEAT_MODE_OFF;
if (message.queryItemValue("repeat").contains("on_all")){
repeatMode = HeosRepeatMode::All;
repeatMode = REPEAT_MODE_ALL;
} else if (message.queryItemValue("repeat").contains("on_one")){
repeatMode = HeosRepeatMode::One;
repeatMode = REPEAT_MODE_ONE;
} else if (message.queryItemValue("repeat").contains("off")){
repeatMode = HeosRepeatMode::Off;
repeatMode = REPEAT_MODE_OFF;
}
emit repeatModeReceived(playerId, repeatMode);
}
@ -403,13 +522,13 @@ void Heos::readData()
if (command.contains("player_state_changed")) {
if (message.hasQueryItem("state")) {
HeosPlayerState playState = HeosPlayerState::Stop;
PLAYER_STATE playState = PLAYER_STATE_STOP;
if (message.queryItemValue("state").contains("play")) {
playState = HeosPlayerState::Play;
playState = PLAYER_STATE_PLAY;
} else if (message.queryItemValue("state").contains("pause")) {
playState = HeosPlayerState::Pause;
playState = PLAYER_STATE_PAUSE;
} else if (message.queryItemValue("state").contains("stop")) {
playState = HeosPlayerState::Stop;
playState = PLAYER_STATE_STOP;
}
emit playStateReceived(playerId, playState);
}
@ -435,13 +554,13 @@ void Heos::readData()
if (command.contains("repeat_mode_changed")) {
if (message.hasQueryItem("repeat")) {
HeosRepeatMode repeatMode = HeosRepeatMode::Off;
REPEAT_MODE repeatMode = REPEAT_MODE_OFF;
if (message.queryItemValue("repeat").contains("on_all")){
repeatMode = HeosRepeatMode::All;
repeatMode = REPEAT_MODE_ALL;
} else if (message.queryItemValue("repeat").contains("on_one")){
repeatMode = HeosRepeatMode::One;
repeatMode = REPEAT_MODE_ONE;
} else if (message.queryItemValue("repeat").contains("off")){
repeatMode = HeosRepeatMode::Off;
repeatMode = REPEAT_MODE_OFF;
}
emit repeatModeReceived(playerId, repeatMode);
}
@ -467,9 +586,3 @@ void Heos::readData()
}
}
}
void Heos::setConnected(const bool &connected)
{
m_connected = connected;
emit connectionStatusChanged();
}

View File

@ -28,49 +28,74 @@
#include <QTcpSocket>
#include "heosplayer.h"
#include "heostypes.h"
class Heos : public QObject
{
Q_OBJECT
public:
enum HeosPlayerState {
Play = 0,
Pause = 1,
Stop = 2
};
enum HeosRepeatMode {
Off = 0,
One = 1,
All = 2
};
explicit Heos(const QHostAddress &hostAddress, QObject *parent = nullptr);
~Heos();
void connectHeos();
void setAddress(QHostAddress address);
QHostAddress getAddress();
bool connected();
void connectHeos();
void getPlayers();
HeosPlayer *getPlayer(int playerId);
// Heos system commands
void registerForChangeEvents(bool state); //By default HEOS speaker does not send Change events. Controller needs to send this command with enable=on when it is ready to receive unsolicit responses from CLI. Please refer to "Driver Initialization" section regarding when to register for change events.
void sendHeartbeat();
void getUserAccount(); //returns current user name in its message field if the user is currently singed in.
void setUserAccount(QString userName, QString password);
void logoutUserAccount();
void rebootSpeaker(); //Using this command controllers can reboot HEOS device. This command can only be used to reboot the HEOS device to which the controller is connected through CLI port.
void prettifyJsonResponse(bool enable); //Helper command to prettify JSON response when user is running CLI controller through telnet.
//Player Get Calls
void getPlayers(); //get a list of players associated with this heos master
void getPlayerState(int playerId);
void setPlayerState(int playerId, HeosPlayerState state);
void getVolume(int playerId);
void setVolume(int playerId, int volume);
void getNowPlayingMedia(int playerId);
void getMute(int playerId);
void setMute(int playerId, bool state);
void setPlayMode(int playerId, HeosRepeatMode repeatMode, bool shuffle); //shuffle and repead mode
void getPlayMode(int playerId);
void getQueue(int playerId);
//Group Get Calls
void getGroups();
void getGroupInfo(int groupId);
void getGroupVolume(int groupId);
void getGroupMute(int groupId);
//Player Set Calls
void setPlayerState(int playerId, PLAYER_STATE state);
void setVolume(int playerId, int volume); //Player volume level 0 to 100
void setMute(int playerId, bool mute);
void setPlayMode(int playerId, REPEAT_MODE repeatMode, bool shuffle); //shuffle and repead mode
void playNext(int playerId);
void playPrevious(int playerId);
void getNowPlayingMedia(int playerId);
void registerForChangeEvents(bool state);
void sendHeartbeat();
void volumeUp(int playerId, int step = 5); //steps 0-10
void volumeDown(int playerId, int step = 5); //steps 0-10
void clearQueue(int playerId);
void moveQueue(int playerId, int sourcQueueId, int destinationQueueId);
void checkForFirmwareUpdate(int playerId);
//Group Set Calls
void setGroupVolume(int groupId, bool volume);
void setGroupMute(int groupId, bool mute);
void toggleGroupMute(int groupId);
void groupVolumeUp(int groupId, int step = 5);
void groupVolumeDown(int groupId, int step = 5);
//Browse Get Commands
void getMusicSources();
void getSourceInfo(SOURCE_ID sourceId);
void getSearchCriteria(SOURCE_ID sourceId);
void browseSource(SOURCE_ID sourceId);
//void search();
private:
bool m_connected = false;
bool m_eventRegistered = false;
QHostAddress m_hostAddress;
QTcpSocket *m_socket = nullptr;
@ -79,14 +104,14 @@ private:
signals:
void playerDiscovered(HeosPlayer *heosPlayer);
void connectionStatusChanged();
void connectionStatusChanged(bool status);
void playStateReceived(int playerId, HeosPlayerState state);
void playStateReceived(int playerId, PLAYER_STATE state);
void shuffleModeReceived(int playerId, bool shuffle);
void repeatModeReceived(int playerId, HeosRepeatMode repeatMode);
void repeatModeReceived(int playerId, REPEAT_MODE repeatMode);
void muteStatusReceived(int playerId, bool mute);
void volumeStatusReceived(int playerId, int volume);
void nowPlayingMediaStatusReceived(int playerId, QString source, QString artist, QString album, QString Song, QString artwork);
void nowPlayingMediaStatusReceived(int playerId, SOURCE_ID source, QString artist, QString album, QString Song, QString artwork);
private slots:
void onConnected();

View File

@ -35,7 +35,6 @@ HeosPlayer::HeosPlayer(int playerId, QString name, QString serialNumber, QObject
m_serialNumber(serialNumber),
m_name(name)
{
}
QString HeosPlayer::name()

137
denon/heostypes.h Normal file
View File

@ -0,0 +1,137 @@
#ifndef HEOSTYPES_H
#define HEOSTYPES_H
#include "extern-plugininfo.h"
enum NETWORK_TYPE {
NETWORK_TYPE_WIRED,
NETWORK_TYPE_WIFI
} ;
enum LINEOUT_LEVEL_TYPE {
LINEOUT_LEVEL_TYPE_VARIABLE = 1,
LINEOUT_LEVEL_TYP_FIXED = 2
};
enum CONTROL_TYPE {
CONTROL_TYPE_NONE = 1,
CONTROL_TYPE_IR = 2,
CONTROL_TYPE_TRIGGER = 3,
CONTROL_TYPE_NETWORK = 4
};
enum PLAYER_STATE {
PLAYER_STATE_PLAY,
PLAYER_STATE_PAUSE,
PLAYER_STATE_STOP
};
enum NOW_PLAYING_OPTIONS {
NOW_PLAYING_OPTION_THUMBS_UP = 11,
NOW_PLAYING_OPTION_THUMBS_DOWN = 12,
NOW_PLAYING_OPTION_ADD_STATION_TO_HEOS_FAVOURITES = 19
};
enum REPEAT_MODE {
REPEAT_MODE_OFF,
REPEAT_MODE_ONE,
REPEAT_MODE_ALL
};
enum PLAYER_ROLE {
PLAYER_ROLE_LEADER,
PLAYER_ROLE_MEMBER
};
enum BROWSE_OPTION {
BROWSE_OPTION_ADD_TRACK_TO_LIBRARY = 1,
BROWSE_OPTION_ADD_ALBUM_TO_LIBRARY = 2,
BROWSE_OPTION_ADD_STATION_TO_LIBRARY = 3,
BROWSE_OPTION_ADD_PLAYLIST_TO_LIBRARY = 4,
BROWSE_OPTION_REMOVE_TRACK_FROM_LIBRARY = 5,
BROWSE_OPTION_REMOVE_ALBUM_FROM_LIBRARY = 6,
BROWSE_OPTION_REMOVE_STATION_FROM_LIBRARY = 7,
BROWSE_OPTION_REMOVE_PLAYLIST_FROM_LIBRARY = 8,
BROWSE_OPTION_CREATE_NEW_STATION = 13,
BROWSE_OPTION_ADD_HEOS_FAVORITES = 19
};
enum SEARCH_CRITERIA { // criteria id returned by 'get_search_criteria' command
SEARCH_CRITERIA_ARTIST,
SEARCH_CRITERIA_ALBUM,
SEARCH_CRITERIA_SONG,
SEARCH_CRITERIA_STATION
};
enum SOURCE_ID {
SOURCE_ID_PANDORA = 1,
SOURCE_ID_RHAPSODY,
SOURCE_ID_TUNEIN,
SOURCE_ID_SPOTIFY,
SOURCE_ID_DEEZER,
SOURCE_ID_NAPSTER,
SOURCE_ID_IHEARTRADIO,
SOURCE_ID_SIRIUS_XM,
SOURCE_ID_SOUNDCLOUD,
SOURCE_ID_TIDAL,
SOURCE_ID_FUTURE_SERVICE_1,
SOURCE_ID_RDIO,
SOURCE_ID_AMAZON_MUSIC,
SOURCE_ID_FUTURE_SERVICE_2,
SOURCE_ID_MOODMIX,
SOURCE_ID_JUKE,
SOURCE_ID_FUTURE_SERVICE_3,
SOURCE_ID_QQMUSIC = 18,
SOURCE_ID_LOCAL_MEDIA = 1024,
SOURCE_ID_HEOS_PLAYLIST = 1025,
SOURCE_ID_HEOS_HISTORY = 1026,
SOURCE_ID_HEOS_AUX = 1027,
SOURCE_ID_HEOS_FAVORITES = 1028
};
struct SearchObject {
int sourceId; //Source id returned by 'get_music_sources' command
QString searchString; //String for search limited to 128 unicode characters and may contain '*' for wildcard if supported by search criteria id
SEARCH_CRITERIA searchCriteria; //Search criteria id returned by 'get_search_criteria' command
int count; //Total number of items available in the container. NOTE: count value of '0' indicates unknown container size. Controllers needs to query until the return payload is empty (returned attribute is 0).
int range; //Range is start and end record index to return. Range parameter is optional. Omitting range parameter returns all records up to a maximum of 50/100 records per response. The default maximum number of records depend on the service type.
int returned; //Number of items returned in current response
};
struct MusicSourceObject {
QString name;
QString image_url;
QString type;
int sourceId;
bool available;
QString serviceUsername;
};
struct PlayerObject {
QString name;
int playerId;
PLAYER_ROLE role;
};
struct GroupObject {
QString name;
int groupId;
QList<PLAYER_ROLE> role;
};
struct SourceContainersObject {
int sourceId;
int containerId;
int range;
int count;
};
struct heosPlayer {
};
struct heosGroup {
};
#endif // HEOSTYPES_H