From c03d67b9ff25e07199b561fa500ba34719296f30 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Mon, 8 Jun 2020 21:27:49 +0200 Subject: [PATCH] denon avr fixed discovery and added async actions --- denon/avrconnection.cpp | 353 ++++++++++++++++++++-------- denon/avrconnection.h | 71 ++++-- denon/integrationplugindenon.cpp | 376 ++++++++++++++++++++++++------ denon/integrationplugindenon.h | 16 +- denon/integrationplugindenon.json | 142 ++++++++++- 5 files changed, 770 insertions(+), 188 deletions(-) diff --git a/denon/avrconnection.cpp b/denon/avrconnection.cpp index 43d69652..22041957 100644 --- a/denon/avrconnection.cpp +++ b/denon/avrconnection.cpp @@ -1,4 +1,4 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2020, nymea GmbH * Contact: contact@nymea.io @@ -43,6 +43,23 @@ AvrConnection::AvrConnection(const QHostAddress &hostAddress, const int &port, Q connect(m_socket, &QTcpSocket::readyRead, this, &AvrConnection::readData); // Note: error signal will be interpreted as function, not as signal in C++11 connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); + + m_commandTimer = new QTimer(this); + m_commandTimer->start(50); // 50ms is the minimum request interval specified + + connect(m_commandTimer, &QTimer::timeout, this, [this] { + if (!m_commandBuffer.isEmpty()) { + QPair command = m_commandBuffer.takeFirst(); + if (m_socket->write(command.second) == -1) { + emit commandExecuted(command.first, false); + qCWarning(dcDenon()) << "Could not execute command" << command.second; + } else { + emit commandExecuted(command.first, true); + } + } else { + m_commandTimer->stop(); + } + }); } AvrConnection::~AvrConnection() @@ -78,56 +95,62 @@ bool AvrConnection::connected() return m_socket->isOpen(); } -void AvrConnection::getAllStatus() +QUuid AvrConnection::getChannel() { - sendCommand("PW?\rSI?\rMV?\rMS?\rMU?\r"); + return sendCommand("SI?\r"); } -void AvrConnection::getChannel() +QUuid AvrConnection::getVolume() { - sendCommand("SI?\r"); + return sendCommand("MV?\r"); } -void AvrConnection::getVolume() +QUuid AvrConnection::getMute() { - sendCommand("MV?\r"); + return sendCommand("MU?\r"); } -void AvrConnection::getMute() +QUuid AvrConnection::getPower() { - sendCommand("MU?\r"); + return sendCommand("PW?\r"); } -void AvrConnection::getPower() +QUuid AvrConnection::getSurroundMode() { - sendCommand("PW?\r"); + return sendCommand("MS?\r"); } -void AvrConnection::getSurroundMode() +QUuid AvrConnection::getPlayBackInfo() { - sendCommand("MS?\r"); + return sendCommand("NSE\r"); } -void AvrConnection::sendCommand(const QByteArray &message) +QUuid AvrConnection::sendCommand(const QByteArray &message) { - m_socket->write(message); + QUuid commandId = QUuid::createUuid(); + + if (!m_commandTimer->isActive()) + m_commandTimer->start(50); + + m_commandBuffer.append(QPair(commandId, message)); + return commandId; } -void AvrConnection::setChannel(const QByteArray &channel) +QUuid AvrConnection::setChannel(const QByteArray &channel) { QByteArray cmd = "SI" + channel + "\r"; - qCDebug(dcDenon) << "Change to channel:" << cmd; - sendCommand(cmd); + qCDebug(dcDenon) << "Change to channel:" << channel; + return sendCommand(cmd); } -void AvrConnection::setVolume(int volume) +QUuid AvrConnection::setVolume(int volume) { qCDebug(dcDenon) << "Set volume" << volume; QByteArray cmd = "MV" + QByteArray::number(volume) + "\r"; - sendCommand(cmd); + return sendCommand(cmd); } -void AvrConnection::setMute(bool mute) +QUuid AvrConnection::setMute(bool mute) { qCDebug(dcDenon) << "Set mute" << mute; QByteArray cmd; @@ -136,10 +159,10 @@ void AvrConnection::setMute(bool mute) } else { cmd = "MUOFF\r"; } - sendCommand(cmd); + return sendCommand(cmd); } -void AvrConnection::setPower(bool power) +QUuid AvrConnection::setPower(bool power) { qCDebug(dcDenon) << "Set power" << power; QByteArray cmd; @@ -148,28 +171,125 @@ void AvrConnection::setPower(bool power) } else { cmd = "PWSTANDBY\r"; } - sendCommand(cmd); + return sendCommand(cmd); } -void AvrConnection::setSurroundMode(const QByteArray &surroundMode) +QUuid AvrConnection::setSurroundMode(const QByteArray &surroundMode) { qCDebug(dcDenon) << "Set surround mode" << surroundMode; QByteArray cmd = "MS" + surroundMode + "\r"; - sendCommand(cmd); + return sendCommand(cmd); } -void AvrConnection::increaseVolume() +QUuid AvrConnection::enableToneControl(bool enabled) +{ + QByteArray cmd; + if (enabled) { + cmd = "PSTONE CTRL ON\r"; + } else { + cmd = "PSTONE CTRL OFF\r"; + } + return sendCommand(cmd); +} + +QUuid AvrConnection::setBassLevel(int level) +{ + QByteArray cmd; + cmd = "PSBAS "; + cmd.append(50 + level); + cmd.append("\r"); + return sendCommand(cmd); +} + +QUuid AvrConnection::setTrebleLevel(int level) +{ + QByteArray cmd; + cmd = "PSTRE "; + cmd.append(50 + level); + cmd.append("\r"); + return sendCommand(cmd); +} + +QUuid AvrConnection::getBassLevel() +{ + return sendCommand("PSBAS ?\r"); +} + +QUuid AvrConnection::getTrebleLevel() +{ + return sendCommand("PSTRE ?\r"); +} + +QUuid AvrConnection::getToneControl() +{ + return sendCommand("PSTONE CTRL ?\r"); +} + +QUuid AvrConnection::play() +{ + return sendCommand("NS9A\r"); +} + +QUuid AvrConnection::pause() +{ + return sendCommand("NS9B\r"); +} + +QUuid AvrConnection::stop() +{ + return sendCommand("NS9C\r"); +} + +QUuid AvrConnection::skipNext() +{ + return sendCommand("NS9D\r"); +} + +QUuid AvrConnection::skipBack() +{ + return sendCommand("NS9E\r"); +} + +QUuid AvrConnection::setRandom(bool on) +{ + QByteArray cmd; + if (on) { + cmd = "NS9K\r"; + } else { + cmd = "NS9M\r"; + } + return sendCommand(cmd); +} + +QUuid AvrConnection::setRepeat(AvrConnection::RepeatMode mode) +{ + QByteArray cmd; + switch (mode) { + case RepeatModeRepeatAll: + cmd = "NS9I\r"; + break; + case RepeatModeRepeatOne: + cmd = "NS9H\r"; + break; + case RepeatModeRepeatNone: + cmd = "NS9J\r"; + break; + } + return sendCommand(cmd); +} + +QUuid AvrConnection::increaseVolume() { qCDebug(dcDenon) << "Execute volume increase"; QByteArray cmd = "MVUP\r"; - sendCommand(cmd); + return sendCommand(cmd); } -void AvrConnection::decreaseVolume() +QUuid AvrConnection::decreaseVolume() { qCDebug(dcDenon) << "Execute volume decrease"; QByteArray cmd = "MVDOWN\r"; - sendCommand(cmd); + return sendCommand(cmd); } void AvrConnection::onConnected() @@ -192,77 +312,112 @@ void AvrConnection::onError(QAbstractSocket::SocketError socketError) void AvrConnection::readData() { - QByteArray data = m_socket->readAll(); - qCDebug(dcDenon) << "Data received" << data; + QString data = QString(m_socket->readAll()); - if (data.contains("MV") && !data.contains("MAX")){ - int index = data.indexOf("MV"); - int volume = data.mid(index+2, 2).toInt(); - emit volumeChanged(volume); - } + QStringList lines = data.split('\r'); + foreach (QString line, lines) { + if(line.isEmpty()) + continue; - if (data.left(2).contains("SI")) { - QByteArray cmd; - if (data.contains("TUNER")) { - cmd = "TUNER"; - } else if (data.contains("DVD")) { - cmd = "DVD"; - } else if (data.contains("BD")) { - cmd = "BD"; - } else if (data.contains("TV")) { - cmd = "TV"; - } else if (data.contains("SAT/CBL")) { - cmd = "SAT/CBL"; - } else if (data.contains("MPLAY")) { - cmd = "MPLAY"; - } else if (data.contains("GAME")) { - cmd = "GAME"; - } else if (data.contains("AUX1")) { - cmd = "AUX1"; - } else if (data.contains("NET")) { - cmd = "NET"; - } else if (data.contains("PANDORA")) { - cmd = "PANDORA"; - } else if (data.contains("SIRIUSXM")) { - cmd = "SIRIUSXM"; - } else if (data.contains("SPOTIFY")) { - cmd = "SPOTIFY"; - } else if (data.contains("FLICKR")) { - cmd = "FLICKR"; - } else if (data.contains("FAVORITES")) { - cmd = "FAVORITES"; - } else if (data.contains("IRADIO")) { - cmd = "IRADIO"; - } else if (data.contains("SERVER")) { - cmd = "SERVER"; - } else if (data.contains("USB/IPOD")) { - cmd = "USB/IPOD"; - } else if (data.contains("IPD")) { - cmd = "IPD"; - } else if (data.contains("IRP")) { - cmd = "IRP"; - } else if (data.contains("FVP")) { - cmd = "FVP"; + qCDebug(dcDenon) << "Data received" << line; + if (line.contains("MV") && !data.contains("MAX")){ + int index = data.indexOf("MV"); + int volume = data.mid(index+2, 2).toInt(); + emit volumeChanged(volume); + + } else if (line.left(2).contains("SI")) { + QByteArray cmd; + if (data.contains("TUNER")) { + cmd = "TUNER"; + } else if (data.contains("DVD")) { + cmd = "DVD"; + } else if (data.contains("BD")) { + cmd = "BD"; + } else if (data.contains("TV")) { + cmd = "TV"; + } else if (data.contains("SAT/CBL")) { + cmd = "SAT/CBL"; + } else if (data.contains("MPLAY")) { + cmd = "MPLAY"; + } else if (data.contains("GAME")) { + cmd = "GAME"; + } else if (data.contains("AUX1")) { + cmd = "AUX1"; + } else if (data.contains("NET")) { + cmd = "NET"; + } else if (data.contains("PANDORA")) { + cmd = "PANDORA"; + } else if (data.contains("SIRIUSXM")) { + cmd = "SIRIUSXM"; + } else if (data.contains("SPOTIFY")) { + cmd = "SPOTIFY"; + } else if (data.contains("FLICKR")) { + cmd = "FLICKR"; + } else if (data.contains("FAVORITES")) { + cmd = "FAVORITES"; + } else if (data.contains("IRADIO")) { + cmd = "IRADIO"; + } else if (data.contains("SERVER")) { + cmd = "SERVER"; + } else if (data.contains("USB/IPOD")) { + cmd = "USB/IPOD"; + } else if (data.contains("IPD")) { + cmd = "IPD"; + } else if (data.contains("IRP")) { + cmd = "IRP"; + } else if (data.contains("FVP")) { + cmd = "FVP"; + } + emit channelChanged(cmd); + } else if (data.contains("PWON")) { + emit powerChanged(true); + } else if (data.contains("PWSTANDBY")) { + emit powerChanged(false); + } else if (data.contains("MUON")) { + emit muteChanged(true); + } else if (data.contains("MUOFF")) { + emit muteChanged(false); + } else if (data.left(2).contains("MS")) { + QString surroundMode = data.remove(0, 2).trimmed(); + qCDebug(dcDenon()) << "Surround mode changed" << surroundMode; + emit surroundModeChanged(surroundMode); + + } else if (data.left(4).contains("NSE0")) { + QString nowPlaying = QString(data).remove(0, 4).trimmed(); + qCDebug(dcDenon()) << "Playbackstatus" << nowPlaying; + if (nowPlaying.contains("Now Playing")) { + emit playBackModeChanged(PlayBackMode::PlayBackModePlaying); + } else { + emit playBackModeChanged(PlayBackMode::PlayBackModeStopped); + } + } else if (data.left(4).contains("NSE1")) { + QString song = QString(data).remove(0, 4).trimmed(); + qCDebug(dcDenon()) << "Song" << song; + emit songChanged(song); + } else if (data.left(4).contains("NSE2")) { + QString artist = QString(data).remove(0, 4).trimmed(); + qCDebug(dcDenon()) << "Artist" << artist; + emit artistChanged(artist); + } else if (data.left(4).contains("NSE4")) { + QString album = QString(data).remove(0, 4).trimmed(); + qCDebug(dcDenon()) << "Album" << album; + emit albumChanged(album); + } else if (data.contains("PSTONE CTRL ON")) { + qCDebug(dcDenon()) << "Tone control is on"; + emit toneControlEnabledChanged(true); + } else if (data.contains("PSTONE CTRL OFF")) { + qCDebug(dcDenon()) << "Tone control is off"; + emit toneControlEnabledChanged(false); + } else if (data.contains("PSBAS")) { + int index = data.indexOf("PSBAS"); + int bass = data.mid(index+6, 2).toInt() - 50; + qCDebug(dcDenon()) << "Bass level" << bass; + emit bassLevelChanged(bass); + } else if (data.contains("PSTRE")) { + int index = data.indexOf("PSTRE"); + int treble = data.mid(index+6, 2).toInt() - 50; + qCDebug(dcDenon()) << "Treble level" << treble; + emit trebleLevelChanged(treble); } - emit channelChanged(cmd); - } - - if (data.contains("PWON")) { - emit powerChanged(true); - } - if (data.contains("PWSTANDBY")) { - emit powerChanged(false); - } - if (data.contains("MUON")) { - emit muteChanged(false); - } - if (data.contains("MUOFF")) { - emit muteChanged(false); - } - - if (data.left(2).contains("MS")) { - data.remove(0, 2); - QByteArray cmd = data; - emit surroundModeChanged(cmd); } } diff --git a/denon/avrconnection.h b/denon/avrconnection.h index d51c71a3..37e4400f 100644 --- a/denon/avrconnection.h +++ b/denon/avrconnection.h @@ -34,11 +34,25 @@ #include #include #include +#include +#include class AvrConnection : public QObject { Q_OBJECT public: + enum RepeatMode { + RepeatModeRepeatAll, + RepeatModeRepeatOne, + RepeatModeRepeatNone + }; + + enum PlayBackMode { + PlayBackModePlaying, + PlayBackModeStopped, + PlayBackModePaused + }; + explicit AvrConnection(const QHostAddress &hostAddress, const int &port = 23, QObject *parent = nullptr); ~AvrConnection(); @@ -49,27 +63,44 @@ public: int port() const; bool connected(); - void getAllStatus(); - void getChannel(); - void getVolume(); - void getMute(); - void getPower(); - void getSurroundMode(); + QUuid getChannel(); + QUuid getVolume(); + QUuid getMute(); + QUuid getPower(); + QUuid getSurroundMode(); + QUuid getPlayBackInfo(); - void setChannel(const QByteArray &channel); - void setVolume(int volume); - void setMute(bool mute); - void setPower(bool power); - void setSurroundMode(const QByteArray &surroundMode); + QUuid setChannel(const QByteArray &channel); + QUuid setVolume(int volume); + QUuid setMute(bool mute); + QUuid setPower(bool power); + QUuid setSurroundMode(const QByteArray &surroundMode); + QUuid enableToneControl(bool enabled); + QUuid setBassLevel(int level); //-6 to +6 + QUuid setTrebleLevel(int level); //-6 to +6 - void increaseVolume(); - void decreaseVolume(); + QUuid getBassLevel(); + QUuid getTrebleLevel(); + QUuid getToneControl(); + + QUuid play(); + QUuid pause(); + QUuid stop(); + QUuid skipNext(); + QUuid skipBack(); + QUuid setRandom(bool on); + QUuid setRepeat(RepeatMode mode); + + QUuid increaseVolume(); + QUuid decreaseVolume(); private: + QTimer *m_commandTimer = nullptr; QTcpSocket *m_socket = nullptr; QHostAddress m_hostAddress; int m_port; + QList> m_commandBuffer; - void sendCommand(const QByteArray &message); + QUuid sendCommand(const QByteArray &message); private slots: void onConnected(); @@ -80,11 +111,19 @@ private slots: signals: void socketErrorOccured(QAbstractSocket::SocketError socketError); void connectionStatusChanged(bool status); + void commandExecuted(const QUuid &commandId, bool success); void volumeChanged(int volume); void muteChanged(bool mute); - void channelChanged(const QByteArray &channel); + void channelChanged(const QString &channel); void powerChanged(bool power); - void surroundModeChanged(const QByteArray &surroundMode); + void surroundModeChanged(const QString &surroundMode); + void songChanged(const QString &song); + void artistChanged(const QString &artist); + void albumChanged(const QString &album); + void playBackModeChanged(AvrConnection::PlayBackMode); + void bassLevelChanged(int level); + void trebleLevelChanged(int level); + void toneControlEnabledChanged(bool enabled); }; #endif // AVRCONNECTION_H diff --git a/denon/integrationplugindenon.cpp b/denon/integrationplugindenon.cpp index c3a26110..88e10f2e 100644 --- a/denon/integrationplugindenon.cpp +++ b/denon/integrationplugindenon.cpp @@ -58,14 +58,15 @@ void IntegrationPluginDenon::init() void IntegrationPluginDenon::discoverThings(ThingDiscoveryInfo *info) { if (info->thingClassId() == AVRX1000ThingClassId) { - if (!hardwareManager()->zeroConfController()->available() || !hardwareManager()->zeroConfController()->enabled()) { - //: Error discovering Denon things - info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Thing discovery is not available.")); - return; - } if (!m_serviceBrowser) { - m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser();; + m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser(); + } + + if (!hardwareManager()->zeroConfController()->available()) { + qCDebug(dcDenon()) << "Error discovering Denon things. Available:" << hardwareManager()->zeroConfController()->available(); + info->finish(Thing::ThingErrorHardwareNotAvailable, "Thing discovery not possible"); + return; } QTimer::singleShot(2000, info, [this, info](){ @@ -196,16 +197,30 @@ void IntegrationPluginDenon::setupThing(ThingSetupInfo *info) AvrConnection *denonConnection = new AvrConnection(address, 23, this); connect(denonConnection, &AvrConnection::connectionStatusChanged, this, &IntegrationPluginDenon::onAvrConnectionChanged); connect(denonConnection, &AvrConnection::socketErrorOccured, this, &IntegrationPluginDenon::onAvrSocketError); + connect(denonConnection, &AvrConnection::commandExecuted, this, &IntegrationPluginDenon::onAvrCommandExecuted); 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); + connect(denonConnection, &AvrConnection::artistChanged, this, &IntegrationPluginDenon::onAvrArtistChanged); + connect(denonConnection, &AvrConnection::albumChanged, this, &IntegrationPluginDenon::onAvrAlbumChanged); + connect(denonConnection, &AvrConnection::songChanged, this, &IntegrationPluginDenon::onAvrSongChanged); + connect(denonConnection, &AvrConnection::playBackModeChanged, this, &IntegrationPluginDenon::onAvrPlayBackModeChanged); + connect(denonConnection, &AvrConnection::bassLevelChanged, this, &IntegrationPluginDenon::onAvrBassLevelChanged); + connect(denonConnection, &AvrConnection::trebleLevelChanged, this, &IntegrationPluginDenon::onAvrTrebleLevelChanged); + connect(denonConnection, &AvrConnection::toneControlEnabledChanged, this, &IntegrationPluginDenon::onAvrToneControlEnabledChanged); m_avrConnections.insert(thing->id(), denonConnection); m_asyncAvrSetups.insert(denonConnection, info); // In case the setup is cancelled before we finish it... connect(info, &QObject::destroyed, this, [this, denonConnection]() { m_asyncAvrSetups.remove(denonConnection); }); + connect(info, &ThingSetupInfo::aborted, this, [this, thing] () { + if (m_avrConnections.contains(thing->id())) { + AvrConnection *connection = m_avrConnections.take(thing->id()); + connection->deleteLater(); + } + }); denonConnection->connectDevice(); return; } else if (thing->thingClassId() == heosThingClassId) { @@ -273,48 +288,120 @@ void IntegrationPluginDenon::executeAction(ThingActionInfo *info) if (thing->thingClassId() == AVRX1000ThingClassId) { AvrConnection *avrConnection = m_avrConnections.value(thing->id()); - if (action.actionTypeId() == AVRX1000PowerActionTypeId) { - + if (action.actionTypeId() == AVRX1000PlayActionTypeId) { + QUuid commandId = avrConnection->play(); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000PauseActionTypeId) { + QUuid commandId = avrConnection->pause(); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000StopActionTypeId) { + QUuid commandId = avrConnection->stop(); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000SkipNextActionTypeId) { + QUuid commandId = avrConnection->skipNext(); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000SkipBackActionTypeId) { + QUuid commandId = avrConnection->skipBack(); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000PowerActionTypeId) { bool power = action.param(AVRX1000PowerActionPowerParamTypeId).value().toBool(); - avrConnection->setPower(power); - return info->finish(Thing::ThingErrorNoError); - + QUuid commandId = avrConnection->setPower(power); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); } else if (action.actionTypeId() == AVRX1000VolumeActionTypeId) { - int vol = action.param(AVRX1000VolumeActionVolumeParamTypeId).value().toInt(); - avrConnection->setVolume(vol); - return info->finish(Thing::ThingErrorNoError); - + QUuid commandId = avrConnection->setVolume(vol); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); } else if (action.actionTypeId() == AVRX1000ChannelActionTypeId) { - - qCDebug(dcDenon) << "Execute update action"; QByteArray channel = action.param(AVRX1000ChannelActionChannelParamTypeId).value().toByteArray(); - avrConnection->setChannel(channel); - return info->finish(Thing::ThingErrorNoError); - + QUuid commandId = avrConnection->setChannel(channel); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); } else if (action.actionTypeId() == AVRX1000IncreaseVolumeActionTypeId) { - - avrConnection->increaseVolume(); - return info->finish(Thing::ThingErrorNoError); - + QUuid commandId = avrConnection->increaseVolume(); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); } else if (action.actionTypeId() == AVRX1000DecreaseVolumeActionTypeId) { - - avrConnection->decreaseVolume(); - return info->finish(Thing::ThingErrorNoError); - + QUuid commandId = avrConnection->decreaseVolume(); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); } else if (action.actionTypeId() == AVRX1000SurroundModeActionTypeId) { - QByteArray surroundMode = action.param(AVRX1000SurroundModeActionSurroundModeParamTypeId).value().toByteArray(); - avrConnection->setSurroundMode(surroundMode); - return info->finish(Thing::ThingErrorNoError); + QUuid commandId = avrConnection->setSurroundMode(surroundMode); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000MuteActionTypeId) { + bool mute = action.param(AVRX1000MuteActionMuteParamTypeId).value().toBool(); + QUuid commandId = avrConnection->setMute(mute); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000RepeatActionTypeId) { + QString repeatMode = action.param(AVRX1000RepeatActionRepeatParamTypeId).value().toString(); + QUuid commandId; + if (repeatMode == "One") { + commandId = avrConnection->setRepeat(AvrConnection::RepeatModeRepeatOne); + } else if (repeatMode == "All") { + commandId = avrConnection->setRepeat(AvrConnection::RepeatModeRepeatAll); + } else { + commandId = avrConnection->setRepeat(AvrConnection::RepeatModeRepeatNone); + } + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000ShuffleActionTypeId) { + bool shuffle = action.param(AVRX1000ShuffleActionShuffleParamTypeId).value().toBool(); + QUuid commandId = avrConnection->setRandom(shuffle); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000PlaybackStatusActionTypeId) { + QString playbackStatus = action.param(AVRX1000PlaybackStatusActionPlaybackStatusParamTypeId).value().toString(); + QUuid commandId; + if (playbackStatus == "Playing") { + commandId = avrConnection->play(); + } else if (playbackStatus == "Stopped") { + commandId = avrConnection->stop(); + } else if (playbackStatus == "Paused") { + commandId = avrConnection->pause(); + } else { + qCWarning(dcDenon()) << "Unrecognized playback status" << playbackStatus; + return info->finish(Thing::ThingErrorHardwareFailure, "Unrecognized command"); + } + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000ToneControlActionTypeId) { + bool enable = action.param(AVRX1000ToneControlActionToneControlParamTypeId).value().toBool(); + QUuid commandId = avrConnection->enableToneControl(enable); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000BassActionTypeId) { + int bass = action.param(AVRX1000BassActionBassParamTypeId).value().toInt(); + QUuid commandId = avrConnection->setBassLevel(bass); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else if (action.actionTypeId() == AVRX1000TrebleActionTypeId) { + int treble = action.param(AVRX1000TrebleActionTrebleParamTypeId).value().toInt(); + QUuid commandId = avrConnection->setTrebleLevel(treble); + connect(info, &ThingActionInfo::aborted, [this, commandId] {m_avrPendingActions.remove(commandId);}); + m_avrPendingActions.insert(commandId, info); + } else { + qCWarning(dcDenon()) << "ActionType not found" << thing->thingClass().name() << action.actionTypeId() ; + return info->finish(Thing::ThingErrorActionTypeNotFound); } - return info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == heosThingClassId) { Heos *heos = m_heosConnections.value(thing->id()); if (action.actionTypeId() == heosRebootActionTypeId) { heos->rebootSpeaker(); return info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcDenon()) << "ActionType not found" << thing->thingClass().name() << action.actionTypeId() ; + return info->finish(Thing::ThingErrorActionTypeNotFound); } } else if (thing->thingClassId() == heosPlayerThingClassId) { @@ -369,16 +456,31 @@ void IntegrationPluginDenon::executeAction(ThingActionInfo *info) heos->playNext(playerId); return info->finish(Thing::ThingErrorNoError); } else { + qCWarning(dcDenon()) << "ActionType not found" << thing->thingClass().name() << action.actionTypeId() ; return info->finish(Thing::ThingErrorActionTypeNotFound); } } else { + qCWarning(dcDenon()) << "ThingClass not found" << thing->thingClass().name() << thing->thingClassId() ; return info->finish(Thing::ThingErrorThingClassNotFound); } } void IntegrationPluginDenon::postSetupThing(Thing *thing) { - if (thing->thingClassId() == heosThingClassId) { + if (thing->thingClassId() == AVRX1000ThingClassId) { + AvrConnection *avrConnection = m_avrConnections.value(thing->id()); + thing->setStateValue(AVRX1000ConnectedStateTypeId, avrConnection->connected()); + avrConnection->getPower(); + avrConnection->getMute(); + avrConnection->getVolume(); + avrConnection->getChannel(); + avrConnection->getSurroundMode(); + avrConnection->getPlayBackInfo(); + avrConnection->getBassLevel(); + avrConnection->getTrebleLevel(); + avrConnection->getToneControl(); + + } else if (thing->thingClassId() == heosThingClassId) { Heos *heos = m_heosConnections.value(thing->id()); thing->setStateValue(heosConnectedStateTypeId, heos->connected()); if (pluginStorage()->childGroups().contains(thing->id().toString())) { @@ -414,13 +516,21 @@ void IntegrationPluginDenon::postSetupThing(Thing *thing) void IntegrationPluginDenon::onPluginTimer() { - foreach(AvrConnection *denonConnection, m_avrConnections.values()) { - if (!denonConnection->connected()) { - denonConnection->connectDevice(); + foreach(AvrConnection *avrConnection, m_avrConnections.values()) { + if (!avrConnection->connected()) { + avrConnection->connectDevice(); } - Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + Thing *thing = myThings().findById(m_avrConnections.key(avrConnection)); if (thing->thingClassId() == AVRX1000ThingClassId) { - denonConnection->getAllStatus(); + avrConnection->getPower(); + avrConnection->getMute(); + avrConnection->getVolume(); + avrConnection->getChannel(); + avrConnection->getSurroundMode(); + avrConnection->getPlayBackInfo(); + avrConnection->getBassLevel(); + avrConnection->getTrebleLevel(); + avrConnection->getToneControl(); } } @@ -437,19 +547,25 @@ void IntegrationPluginDenon::onPluginTimer() void IntegrationPluginDenon::onAvrConnectionChanged(bool status) { AvrConnection *denonConnection = static_cast(sender()); - Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); - if (!thing) + + // if the thing and from the first setup + if (m_asyncAvrSetups.contains(denonConnection)) { + // and ist connected + if (status) { + ThingSetupInfo *info = m_asyncAvrSetups.take(denonConnection); + info->thing()->setStateValue(AVRX1000ConnectedStateTypeId, true); + info->finish(Thing::ThingErrorNoError); + } return; + } + + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing) { + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } if (thing->thingClassId() == AVRX1000ThingClassId) { - // if the thing is connected - if (status) { - // and from the first setup - if (m_asyncAvrSetups.contains(denonConnection)) { - ThingSetupInfo *info = m_asyncAvrSetups.take(denonConnection); - info->finish(Thing::ThingErrorNoError); - } - } thing->setStateValue(AVRX1000ConnectedStateTypeId, denonConnection->connected()); } } @@ -458,15 +574,17 @@ void IntegrationPluginDenon::onAvrVolumeChanged(int volume) { AvrConnection *denonConnection = static_cast(sender()); Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); - if (!thing) + if (!thing) { + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; return; + } if (thing->thingClassId() == AVRX1000ThingClassId) { thing->setStateValue(AVRX1000VolumeStateTypeId, volume); } } -void IntegrationPluginDenon::onAvrChannelChanged(const QByteArray &channel) +void IntegrationPluginDenon::onAvrChannelChanged(const QString &channel) { AvrConnection *denonConnection = static_cast(sender()); Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); @@ -482,8 +600,10 @@ void IntegrationPluginDenon::onAvrMuteChanged(bool mute) { AvrConnection *denonConnection = static_cast(sender()); Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); - if (!thing) + if (!thing) { + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; return; + } if (thing->thingClassId() == AVRX1000ThingClassId) { thing->setStateValue(AVRX1000MuteStateTypeId, mute); @@ -502,37 +622,157 @@ void IntegrationPluginDenon::onAvrPowerChanged(bool power) } } -void IntegrationPluginDenon::onAvrSurroundModeChanged(const QByteArray &surroundMode) +void IntegrationPluginDenon::onAvrSurroundModeChanged(const QString &surroundMode) { AvrConnection *denonConnection = static_cast(sender()); Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); - if (!thing) + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; return; - + } if (thing->thingClassId() == AVRX1000ThingClassId) { thing->setStateValue(AVRX1000SurroundModeStateTypeId, surroundMode); } } +void IntegrationPluginDenon::onAvrSongChanged(const QString &song) +{ + AvrConnection *denonConnection = static_cast(sender()); + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } + + if (thing->thingClassId() == AVRX1000ThingClassId) { + thing->setStateValue(AVRX1000TitleStateTypeId, song); + } +} + +void IntegrationPluginDenon::onAvrArtistChanged(const QString &artist) +{ + AvrConnection *denonConnection = static_cast(sender()); + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } + + if (thing->thingClassId() == AVRX1000ThingClassId) { + thing->setStateValue(AVRX1000ArtistStateTypeId, artist); + } +} + +void IntegrationPluginDenon::onAvrAlbumChanged(const QString &album) +{ + AvrConnection *denonConnection = static_cast(sender()); + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } + if (thing->thingClassId() == AVRX1000ThingClassId) { + thing->setStateValue(AVRX1000CollectionStateTypeId, album); + } +} + +void IntegrationPluginDenon::onAvrBassLevelChanged(int level) +{ + AvrConnection *denonConnection = static_cast(sender()); + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } + if (thing->thingClassId() == AVRX1000ThingClassId) { + thing->setStateValue(AVRX1000BassStateTypeId, level); + } +} + +void IntegrationPluginDenon::onAvrTrebleLevelChanged(int level) +{ + AvrConnection *denonConnection = static_cast(sender()); + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } + if (thing->thingClassId() == AVRX1000ThingClassId) { + thing->setStateValue(AVRX1000TrebleStateTypeId, level); + } +} + +void IntegrationPluginDenon::onAvrToneControlEnabledChanged(bool enabled) +{ + AvrConnection *denonConnection = static_cast(sender()); + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } + if (thing->thingClassId() == AVRX1000ThingClassId) { + thing->setStateValue(AVRX1000ToneControlStateTypeId, enabled); + } +} + +void IntegrationPluginDenon::onAvrPlayBackModeChanged(AvrConnection::PlayBackMode mode) +{ + AvrConnection *denonConnection = static_cast(sender()); + Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); + if (!thing){ + qCWarning(dcDenon()) << "Could not find a thing associated to this AVR connection"; + return; + } + + if (thing->thingClassId() == AVRX1000ThingClassId) { + switch (mode) { + case AvrConnection::PlayBackModePlaying: + thing->setStateValue(AVRX1000PlaybackStatusStateTypeId, "Playing"); + break; + case AvrConnection::PlayBackModePaused: + thing->setStateValue(AVRX1000PlaybackStatusStateTypeId, "Paused"); + break; + case AvrConnection::PlayBackModeStopped: + thing->setStateValue(AVRX1000PlaybackStatusStateTypeId, "Stopped"); + break; + } + } +} + void IntegrationPluginDenon::onAvrSocketError() { AvrConnection *denonConnection = static_cast(sender()); - Thing *thing = myThings().findById(m_avrConnections.key(denonConnection)); - if (!thing) - return; - if (thing->thingClassId() == AVRX1000ThingClassId) { + // Check if setup running for this thing + if (m_asyncAvrSetups.contains(denonConnection)) { + ThingSetupInfo *info = m_asyncAvrSetups.take(denonConnection); + qCWarning(dcDenon()) << "Could not add thing. The setup failed."; + info->finish(Thing::ThingErrorHardwareFailure); + // Delete the connection, the thing will not be added and + // the connection will be created in the next setup + denonConnection->deleteLater(); + } +} - // Check if setup running for this thing - if (m_asyncAvrSetups.contains(denonConnection)) { - ThingSetupInfo *info = m_asyncAvrSetups.take(denonConnection); - qCWarning(dcDenon()) << "Could not add thing. The setup failed."; - info->finish(Thing::ThingErrorHardwareFailure); - // Delete the connection, the thing will not be added and - // the connection will be created in the next setup - denonConnection->deleteLater(); - m_avrConnections.remove(thing->id()); +void IntegrationPluginDenon::onAvrCommandExecuted(const QUuid &commandId, bool success) +{ + if (m_avrPendingActions.contains(commandId)) { + ThingActionInfo *info = m_avrPendingActions.take(commandId); + if (success){ + if(info->action().actionTypeId() == AVRX1000PlayActionTypeId) { + info->thing()->setStateValue(AVRX1000PlaybackStatusStateTypeId, "Playing"); + } else if(info->action().actionTypeId() == AVRX1000PauseActionTypeId) { + info->thing()->setStateValue(AVRX1000PlaybackStatusStateTypeId, "Paused"); + } else if(info->action().actionTypeId() == AVRX1000StopActionTypeId) { + info->thing()->setStateValue(AVRX1000PlaybackStatusStateTypeId, "Stopped"); + } else if(info->action().actionTypeId() == AVRX1000PlaybackStatusActionTypeId) { + info->thing()->setStateValue(AVRX1000PlaybackStatusStateTypeId, info->action().param(AVRX1000PlaybackStatusActionPlaybackStatusParamTypeId).value()); + } + info->finish(Thing::ThingErrorNoError); + + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); } } } @@ -663,7 +903,7 @@ void IntegrationPluginDenon::onHeosRepeatModeReceived(int playerId, REPEAT_MODE void IntegrationPluginDenon::onHeosShuffleModeReceived(int playerId, bool shuffle) { foreach(Thing *thing, myThings().filterByParam(heosPlayerThingPlayerIdParamTypeId, playerId)) { - thing->setStateValue(heosPlayerMuteStateTypeId, shuffle); + thing->setStateValue(heosPlayerShuffleStateTypeId, shuffle); break; } } diff --git a/denon/integrationplugindenon.h b/denon/integrationplugindenon.h index 6c1b661a..bcd8333f 100644 --- a/denon/integrationplugindenon.h +++ b/denon/integrationplugindenon.h @@ -90,7 +90,8 @@ private: QHash m_asyncActions; QUrl m_notificationUrl; - QHash m_pendingActions; + QHash m_heosPendingActions; + QHash m_avrPendingActions; QHash m_pendingGetSourcesRequest; QHash m_pendingBrowseResult; // QString = containerId or sourceId @@ -128,11 +129,20 @@ private slots: void onAvrConnectionChanged(bool status); void onAvrSocketError(); + void onAvrCommandExecuted(const QUuid &commandId, bool success); + void onAvrVolumeChanged(int volume); - void onAvrChannelChanged(const QByteArray &channel); + void onAvrChannelChanged(const QString &channel); void onAvrMuteChanged(bool mute); void onAvrPowerChanged(bool power); - void onAvrSurroundModeChanged(const QByteArray &surroundMode); + void onAvrSurroundModeChanged(const QString &surroundMode); + void onAvrSongChanged(const QString &song); + void onAvrArtistChanged(const QString &artist); + void onAvrAlbumChanged(const QString &album); + void onAvrPlayBackModeChanged(AvrConnection::PlayBackMode mode); + void onAvrBassLevelChanged(int level); + void onAvrTrebleLevelChanged(int level); + void onAvrToneControlEnabledChanged(bool enabled); void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); }; diff --git a/denon/integrationplugindenon.json b/denon/integrationplugindenon.json index 68c8d78c..a01614da 100644 --- a/denon/integrationplugindenon.json +++ b/denon/integrationplugindenon.json @@ -22,7 +22,7 @@ "name": "AVRX1000", "displayName": "AVR X1000", "createMethods": ["discovery"], - "interfaces": ["extendedvolumecontroller", "connectable", "power"], + "interfaces": ["mediaplayer", "mediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable", "power"], "paramTypes": [ { "id": "cb6eeeb0-3d75-43b6-8177-b5ac19648557", @@ -49,7 +49,7 @@ "cached": false }, { - "displayName": "power", + "displayName": "Power", "id": "1cdb6b54-6831-4900-95b2-c78f64497701", "name": "power", "displayNameEvent": "Power changed", @@ -80,6 +80,40 @@ "maxValue": 100, "writable": true }, + { + "displayName": "Tone control", + "id": "d57c1e5e-2cc9-4638-999c-1523f16dbb83", + "name": "toneControl", + "displayNameEvent": "Tone control changed", + "displayNameAction": "Set tone control", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "displayName": "Bass", + "id": "2c92b22e-d5b2-4991-a523-64222bffc9e7", + "name": "bass", + "displayNameEvent": "Bass changed", + "displayNameAction": "Set bass", + "type": "int", + "defaultValue": 0, + "minValue": -50, + "maxValue": 49, + "writable": true + }, + { + "displayName": "Treble", + "id": "38a3be02-6ed4-4a84-903e-eb923b933989", + "name": "treble", + "displayNameEvent": "Treble changed", + "displayNameAction": "Set treble", + "type": "int", + "defaultValue": 0, + "minValue": -50, + "maxValue": 49, + "writable": true + }, { "displayName": "Channel", "id": "f29ffa2c-31d6-4d88-b160-a38288c82ce1", @@ -136,6 +170,85 @@ "MATRIX" ], "defaultValue": "MOVIE" + }, + { + "id": "50b34df4-90f7-41aa-9a57-6f2d31a18430", + "name": "artist", + "displayName": "Artist", + "displayNameEvent": "Artist changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "d4f14d93-8cae-4e1b-9ee7-a5c21c0211df", + "name": "collection", + "displayName": "Album", + "displayNameEvent": "Album changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "d32493d8-5faf-4c7a-ba94-847dc9b81615", + "name": "title", + "displayName": "Title", + "displayNameEvent": "Title changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "6c4a208b-5b04-40cc-b14b-8f79aff30307", + "name": "artwork", + "displayName": "Artwork", + "displayNameEvent": "Artwork changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "2f372374-16f3-4900-afdc-834f51075d07", + "name": "playerType", + "displayName": "Player type", + "displayNameEvent": "Player type changed", + "possibleValues": [ + "audio", + "video" + ], + "type": "QString", + "defaultValue": "audio" + }, + { + "id": "8ef6708c-812a-4e6a-a608-9e480aa3b7bf", + "name": "playbackStatus", + "displayName": "Playback status", + "displayNameEvent": "Playback status changed", + "displayNameAction": "Set playback status", + "type": "QString", + "defaultValue": "Stopped", + "possibleValues": ["Playing", "Paused", "Stopped"], + "cached": false, + "writable": true + }, + { + "id": "8ad33cb9-e758-433d-a013-2e4d43157c92", + "name": "shuffle", + "displayName": "Shuffle", + "displayNameEvent": "Shuffle changed", + "displayNameAction": "Set shuffle", + "type": "bool", + "defaultValue": false, + "cached": false, + "writable": true + }, + { + "id": "9478987b-14e4-4572-a059-a18a5a9db229", + "name": "repeat", + "displayName": "Repeat mode", + "displayNameEvent": "Repeat mode changed", + "displayNameAction": "Set repeat mode", + "type": "QString", + "defaultValue": "None", + "possibleValues": ["None", "One", "All"], + "cached": false, + "writable": true } ], "actionTypes": [ @@ -164,6 +277,31 @@ "type": "int" } ] + }, + { + "id": "3f2eb789-918c-475a-a295-14c0c24338b8", + "name": "skipBack", + "displayName": "Skip back" + }, + { + "id": "ddab0869-5b90-4fd4-9c40-9ad57101b87c", + "name": "stop", + "displayName": "Stop" + }, + { + "id": "d04eb30b-838d-4fbd-b781-c01005b59756", + "name": "play", + "displayName": "Play" + }, + { + "id": "3de38047-006f-4d97-9326-08bb5ad79b05", + "name": "pause", + "displayName": "Pause" + }, + { + "id": "bf9664e4-a53e-474c-afcc-88f25e6fe365", + "name": "skipNext", + "displayName": "Skip next" } ] },