From 0eb0933b34777dde38168e2acf4defae13371821 Mon Sep 17 00:00:00 2001 From: nymea Date: Fri, 12 Jul 2019 18:14:19 +0200 Subject: [PATCH] fixed heos rediscovery --- denon/denon.pro | 3 +- denon/deviceplugindenon.cpp | 169 ++++++++++++----- denon/deviceplugindenon.h | 11 +- denon/heos.cpp | 363 +++++++++++++++++++++++------------- denon/heos.h | 79 +++++--- denon/heosplayer.cpp | 1 - denon/heostypes.h | 137 ++++++++++++++ 7 files changed, 560 insertions(+), 203 deletions(-) create mode 100644 denon/heostypes.h diff --git a/denon/denon.pro b/denon/denon.pro index 34ff584a..bc6a7b8d 100644 --- a/denon/denon.pro +++ b/denon/denon.pro @@ -14,4 +14,5 @@ HEADERS += \ deviceplugindenon.h \ heos.h \ heosplayer.h \ - avrconnection.h + avrconnection.h \ + heostypes.h diff --git a/denon/deviceplugindenon.cpp b/denon/deviceplugindenon.cpp index 734d361c..be3b9c7c 100644 --- a/denon/deviceplugindenon.cpp +++ b/denon/deviceplugindenon.cpp @@ -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 heosDescriptors; - QList 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(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; } diff --git a/denon/deviceplugindenon.h b/denon/deviceplugindenon.h index fbbeb121..84536d55 100644 --- a/denon/deviceplugindenon.h +++ b/denon/deviceplugindenon.h @@ -62,7 +62,8 @@ private: QHash m_avrConnections; QHash m_heos; - QList m_asyncSetups; + QList m_asyncAvrSetups; + QList m_asyncHeosSetups; QHash m_playerIds; QHash 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); diff --git a/denon/heos.cpp b/denon/heos.cpp index 01bef7c6..b5d5a31d 100644 --- a/denon/heos.cpp +++ b/denon/heos.cpp @@ -26,6 +26,7 @@ #include #include #include +#include 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(); -} diff --git a/denon/heos.h b/denon/heos.h index 4cdc1755..59e7c052 100644 --- a/denon/heos.h +++ b/denon/heos.h @@ -28,49 +28,74 @@ #include #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(); diff --git a/denon/heosplayer.cpp b/denon/heosplayer.cpp index 7ac95c89..51ed1bcb 100644 --- a/denon/heosplayer.cpp +++ b/denon/heosplayer.cpp @@ -35,7 +35,6 @@ HeosPlayer::HeosPlayer(int playerId, QString name, QString serialNumber, QObject m_serialNumber(serialNumber), m_name(name) { - } QString HeosPlayer::name() diff --git a/denon/heostypes.h b/denon/heostypes.h new file mode 100644 index 00000000..ba58db89 --- /dev/null +++ b/denon/heostypes.h @@ -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 role; +}; + +struct SourceContainersObject { + int sourceId; + int containerId; + int range; + int count; +}; + +struct heosPlayer { + +}; + +struct heosGroup { + +}; + +#endif // HEOSTYPES_H