Merge PR #102: Denon Plug-In: Add heos devices

This commit is contained in:
Jenkins 2019-09-19 12:27:59 +02:00
commit e52ac488ab
12 changed files with 2278 additions and 327 deletions

261
denon/avrconnection.cpp Normal file
View File

@ -0,0 +1,261 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@nymea.io> *
* Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "avrconnection.h"
#include "extern-plugininfo.h"
AvrConnection::AvrConnection(const QHostAddress &hostAddress, const int &port, QObject *parent) :
QObject(parent),
m_hostAddress(hostAddress),
m_port(port)
{
m_socket = new QTcpSocket(this);
connect(m_socket, &QTcpSocket::connected, this, &AvrConnection::onConnected);
connect(m_socket, &QTcpSocket::disconnected, this, &AvrConnection::onDisconnected);
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)));
}
AvrConnection::~AvrConnection()
{
m_socket->close();
}
void AvrConnection::connectDevice()
{
if (m_socket->state() == QAbstractSocket::ConnectingState) {
return;
}
m_socket->connectToHost(m_hostAddress, m_port);
}
void AvrConnection::disconnectDevice()
{
m_socket->close();
}
QHostAddress AvrConnection::hostAddress() const
{
return m_hostAddress;
}
int AvrConnection::port() const
{
return m_port;
}
bool AvrConnection::connected()
{
return m_socket->isOpen();
}
void AvrConnection::getAllStatus()
{
sendCommand("PW?\rSI?\rMV?\rMS?\rMU?\r");
}
void AvrConnection::getChannel()
{
sendCommand("SI?\r");
}
void AvrConnection::getVolume()
{
sendCommand("MV?\r");
}
void AvrConnection::getMute()
{
sendCommand("MU?\r");
}
void AvrConnection::getPower()
{
sendCommand("PW?\r");
}
void AvrConnection::getSurroundMode()
{
sendCommand("MS?\r");
}
void AvrConnection::sendCommand(const QByteArray &message)
{
m_socket->write(message);
}
void AvrConnection::setChannel(const QByteArray &channel)
{
QByteArray cmd = "SI" + channel + "\r";
qCDebug(dcDenon) << "Change to channel:" << cmd;
sendCommand(cmd);
}
void AvrConnection::setVolume(int volume)
{
qCDebug(dcDenon) << "Set volume" << volume;
QByteArray cmd = "MV" + QByteArray::number(volume) + "\r";
sendCommand(cmd);
}
void AvrConnection::setMute(bool mute)
{
qCDebug(dcDenon) << "Set mute" << mute;
QByteArray cmd;
if (mute) {
cmd = "MUON\r";
} else {
cmd = "MUOFF\r";
}
sendCommand(cmd);
}
void AvrConnection::setPower(bool power)
{
qCDebug(dcDenon) << "Set power" << power;
QByteArray cmd;
if (power) {
cmd = "PWON\r";
} else {
cmd = "PWSTANDBY\r";
}
sendCommand(cmd);
}
void AvrConnection::setSurroundMode(const QByteArray &surroundMode)
{
qCDebug(dcDenon) << "Set surround mode" << surroundMode;
QByteArray cmd = "MS" + surroundMode + "\r";
sendCommand(cmd);
}
void AvrConnection::increaseVolume()
{
qCDebug(dcDenon) << "Execute volume increase";
QByteArray cmd = "MVUP\r";
sendCommand(cmd);
}
void AvrConnection::decreaseVolume()
{
qCDebug(dcDenon) << "Execute volume decrease";
QByteArray cmd = "MVDOWN\r";
sendCommand(cmd);
}
void AvrConnection::onConnected()
{
qCDebug(dcDenon) << "connected successfully to" << hostAddress().toString() << port();
emit connectionStatusChanged(true);
}
void AvrConnection::onDisconnected()
{
qCDebug(dcDenon) << "disconnected from" << hostAddress().toString() << port();
emit connectionStatusChanged(false);
}
void AvrConnection::onError(QAbstractSocket::SocketError socketError)
{
qCWarning(dcDenon) << "socket error:" << socketError << m_socket->errorString();
emit socketErrorOccured(socketError);
}
void AvrConnection::readData()
{
QByteArray data = m_socket->readAll();
qCDebug(dcDenon) << "Data received" << data;
if (data.contains("MV") && !data.contains("MAX")){
int index = data.indexOf("MV");
int volume = data.mid(index+2, 2).toInt();
emit volumeChanged(volume);
}
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";
}
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);
}
}

View File

@ -1,7 +1,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> * * Copyright (C) 2015 Simon Stürz <simon.stuerz@nymea.io> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> * * Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* * * *
* This file is part of nymea. * * This file is part of nymea. *
* * * *
@ -21,36 +21,48 @@
* * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DENONCONNECTION_H #ifndef AVRCONNECTION_H
#define DENONCONNECTION_H #define AVRCONNECTION_H
#include <QObject> #include <QObject>
#include <QTcpSocket> #include <QTcpSocket>
#include <QHostAddress> #include <QHostAddress>
class DenonConnection : public QObject class AvrConnection : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit DenonConnection(const QHostAddress &hostAddress, const int &port = 23, QObject *parent = 0); explicit AvrConnection(const QHostAddress &hostAddress, const int &port = 23, QObject *parent = nullptr);
~DenonConnection(); ~AvrConnection();
void connectDenon(); void connectDevice();
void disconnectDenon(); void disconnectDevice();
QHostAddress hostAddress() const; QHostAddress hostAddress() const;
int port() const; int port() const;
bool connected(); bool connected();
void sendData(const QByteArray &message); void getAllStatus();
void getChannel();
void getVolume();
void getMute();
void getPower();
void getSurroundMode();
void setChannel(const QByteArray &channel);
void setVolume(int volume);
void setMute(bool mute);
void setPower(bool power);
void setSurroundMode(const QByteArray &surroundMode);
void increaseVolume();
void decreaseVolume();
private: private:
QTcpSocket *m_socket; QTcpSocket *m_socket = nullptr;
QHostAddress m_hostAddress; QHostAddress m_hostAddress;
int m_port; int m_port;
bool m_connected;
void sendCommand(const QByteArray &message);
private slots: private slots:
void onConnected(); void onConnected();
@ -58,13 +70,14 @@ private slots:
void onError(QAbstractSocket::SocketError socketError); void onError(QAbstractSocket::SocketError socketError);
void readData(); void readData();
void setConnected(const bool &connected);
signals: signals:
void socketErrorOccured(QAbstractSocket::SocketError socketError); void socketErrorOccured(QAbstractSocket::SocketError socketError);
void connectionStatusChanged(); void connectionStatusChanged(bool status);
void dataReady(const QByteArray &data); void volumeChanged(int volume);
void muteChanged(bool mute);
void channelChanged(const QByteArray &channel);
void powerChanged(bool power);
void surroundModeChanged(const QByteArray &surroundMode);
}; };
#endif // DENONCONNECTION_H #endif // AVRCONNECTION_H

View File

@ -6,8 +6,13 @@ TARGET = $$qtLibraryTarget(nymea_deviceplugindenon)
SOURCES += \ SOURCES += \
deviceplugindenon.cpp \ deviceplugindenon.cpp \
denonconnection.cpp heos.cpp \
heosplayer.cpp \
avrconnection.cpp
HEADERS += \ HEADERS += \
deviceplugindenon.h \ deviceplugindenon.h \
denonconnection.h heos.h \
heosplayer.h \
avrconnection.h \
heostypes.h

View File

@ -1,108 +0,0 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "denonconnection.h"
#include "extern-plugininfo.h"
DenonConnection::DenonConnection(const QHostAddress &hostAddress, const int &port, QObject *parent) :
QObject(parent),
m_hostAddress(hostAddress),
m_port(port),
m_connected(false)
{
m_socket = new QTcpSocket(this);
connect(m_socket, &QTcpSocket::connected, this, &DenonConnection::onConnected);
connect(m_socket, &QTcpSocket::disconnected, this, &DenonConnection::onDisconnected);
connect(m_socket, &QTcpSocket::readyRead, this, &DenonConnection::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)));
}
DenonConnection::~DenonConnection()
{
m_socket->close();
}
void DenonConnection::connectDenon()
{
if (m_socket->state() == QAbstractSocket::ConnectingState) {
return;
}
m_socket->connectToHost(m_hostAddress, m_port);
}
void DenonConnection::disconnectDenon()
{
m_socket->close();
}
QHostAddress DenonConnection::hostAddress() const
{
return m_hostAddress;
}
int DenonConnection::port() const
{
return m_port;
}
bool DenonConnection::connected()
{
return m_connected;
}
void DenonConnection::sendData(const QByteArray &message)
{
m_socket->write(message);
}
void DenonConnection::onConnected()
{
qCDebug(dcDenon) << "connected successfully to" << hostAddress().toString() << port();
setConnected(true);
}
void DenonConnection::onDisconnected()
{
qCDebug(dcDenon) << "disconnected from" << hostAddress().toString() << port();
setConnected(false);
}
void DenonConnection::onError(QAbstractSocket::SocketError socketError)
{
qCWarning(dcDenon) << "socket error:" << socketError << m_socket->errorString();
emit socketErrorOccured(socketError);
}
void DenonConnection::readData()
{
QByteArray data = m_socket->readAll();
emit dataReady(QString(data).toUtf8());
}
void DenonConnection::setConnected(const bool &connected)
{
m_connected = connected;
emit connectionStatusChanged();
}

View File

@ -1,7 +1,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> * * Copyright (C) 2015 Simon Stürz <simon.stuerz@nymea.io> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> * * Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* * * *
* This file is part of nymea. * * This file is part of nymea. *
* * * *
@ -21,112 +21,291 @@
* * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*!
\page denon.html
\title Denon
\brief Plugin for Denon AV and Heos Devices
\ingroup plugins
\ingroup nymea-plugins
This plug-in supports the
\l {http://www.denon.de/de/product/hometheater/avreceivers/avrx1000}{Denon AV Amplifier AVR-X1000}
\chapter Plugin properties
Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses}
and \l{Vendor}{Vendors} of this \l{DevicePlugin}.
For more details how to read this JSON file please check out the documentation for \l{The plugin JSON File}.
\quotefile plugins/deviceplugins/denon/deviceplugindenon.json
*/
#include "deviceplugindenon.h" #include "deviceplugindenon.h"
#include "plugininfo.h" #include "plugininfo.h"
#include "devices/device.h"
#include "network/networkaccessmanager.h"
#include "network/upnp/upnpdiscovery.h"
#include "network/upnp/upnpdiscoveryreply.h"
#include "platform/platformzeroconfcontroller.h"
#include "network/zeroconf/zeroconfservicebrowser.h"
#include <QDebug>
#include <QStringList>
#include <QJsonDocument>
#include <QTimer>
DevicePluginDenon::DevicePluginDenon() DevicePluginDenon::DevicePluginDenon()
{ {
} }
DevicePluginDenon::~DevicePluginDenon() Device::DeviceError DevicePluginDenon::discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params)
{ {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); Q_UNUSED(params)
}
void DevicePluginDenon::init() if (deviceClassId == AVRX1000DeviceClassId) {
{ if (!m_serviceBrowser) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(15); m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser();
connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginDenon::onPluginTimer); connect(m_serviceBrowser, &ZeroConfServiceBrowser::serviceEntryAdded, this, &DevicePluginDenon::onAvahiServiceEntryAdded);
connect(m_serviceBrowser, &ZeroConfServiceBrowser::serviceEntryRemoved, this, &DevicePluginDenon::onAvahiServiceEntryRemoved);
}
QStringList discoveredIds;
QList<DeviceDescriptor> deviceDescriptors;
foreach (const ZeroConfServiceEntry &service, m_serviceBrowser->serviceEntries()) {
if (service.txt().contains("am=AVRX1000")) {
QString id = service.name().split("@").first();
QString name = service.name().split("@").last();
QString address = service.hostAddress().toString();
qCDebug(dcDenon) << "service discovered" << name << "ID:" << id;
if (discoveredIds.contains(id))
break;
discoveredIds.append(id);
DeviceDescriptor deviceDescriptor(AVRX1000DeviceClassId, name, address);
ParamList params;
params.append(Param(AVRX1000DeviceIpParamTypeId, address));
params.append(Param(AVRX1000DeviceIdParamTypeId, id));
deviceDescriptor.setParams(params);
foreach (Device *existingDevice, myDevices()) {
if (existingDevice->paramValue(AVRX1000DeviceIdParamTypeId).toString() == id) {
deviceDescriptor.setDeviceId(existingDevice->id());
break;
}
}
deviceDescriptors.append(deviceDescriptor);
}
}
emit devicesDiscovered(AVRX1000DeviceClassId, deviceDescriptors);
return Device::DeviceErrorAsync;
}
if (deviceClassId == heosDeviceClassId) {
/*
* The HEOS products can be discovered using the UPnP SSDP protocol. Through discovery,
* the IP address of the HEOS products can be retrieved. Once the IP address is retrieved,
* a telnet connection to port 1255 can be opened to access the HEOS CLI and control the HEOS system.
* The HEOS product IP address can also be set statically and manually programmed into the control system.
* Search target name (ST) in M-SEARCH discovery request is 'urn:schemas-denon-com:device:ACT-Denon:1'.
*/
UpnpDiscoveryReply *reply = hardwareManager()->upnpDiscovery()->discoverDevices();
connect(reply, &UpnpDiscoveryReply::finished, this, &DevicePluginDenon::onUpnpDiscoveryFinished);
return Device::DeviceErrorAsync;
}
return Device::DeviceErrorDeviceClassNotFound;
} }
Device::DeviceSetupStatus DevicePluginDenon::setupDevice(Device *device) Device::DeviceSetupStatus DevicePluginDenon::setupDevice(Device *device)
{ {
qCDebug(dcDenon) << "Setup Denon device" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString(); if(!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
// Check if we already have a denon device connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginDenon::onPluginTimer);
if (!myDevices().isEmpty()) {
qCWarning(dcDenon) << "Could not add denon device. Only one denon device allowed.";
return Device::DeviceSetupStatusFailure;
} }
if (device->deviceClassId() == AVRX1000DeviceClassId) {
qCDebug(dcDenon) << "Setup Denon device" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString();
QHostAddress address(device->paramValue(AVRX1000DeviceIpParamTypeId).toString()); QHostAddress address(device->paramValue(AVRX1000DeviceIpParamTypeId).toString());
if (address.isNull()) { if (address.isNull()) {
qCWarning(dcDenon) << "Could not parse ip address" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString(); qCWarning(dcDenon) << "Could not parse ip address" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString();
return Device::DeviceSetupStatusFailure; return Device::DeviceSetupStatusFailure;
} }
m_device = device; AvrConnection *denonConnection = new AvrConnection(address, 23, this);
m_denonConnection = new DenonConnection(address, 23, this); connect(denonConnection, &AvrConnection::connectionStatusChanged, this, &DevicePluginDenon::onAvrConnectionChanged);
connect(m_denonConnection.data(), &DenonConnection::connectionStatusChanged, this, &DevicePluginDenon::onConnectionChanged); connect(denonConnection, &AvrConnection::socketErrorOccured, this, &DevicePluginDenon::onAvrSocketError);
connect(m_denonConnection.data(), &DenonConnection::socketErrorOccured, this, &DevicePluginDenon::onSocketError); connect(denonConnection, &AvrConnection::channelChanged, this, &DevicePluginDenon::onAvrChannelChanged);
connect(m_denonConnection.data(), &DenonConnection::dataReady, this, &DevicePluginDenon::onDataReceived); connect(denonConnection, &AvrConnection::powerChanged, this, &DevicePluginDenon::onAvrPowerChanged);
connect(denonConnection, &AvrConnection::volumeChanged, this, &DevicePluginDenon::onAvrVolumeChanged);
m_asyncSetups.append(m_denonConnection); connect(denonConnection, &AvrConnection::surroundModeChanged, this, &DevicePluginDenon::onAvrSurroundModeChanged);
m_denonConnection->connectDenon(); connect(denonConnection, &AvrConnection::muteChanged, this, &DevicePluginDenon::onAvrMuteChanged);
m_asyncAvrSetups.append(denonConnection);
denonConnection->connectDevice();
m_avrConnections.insert(device, denonConnection);
return Device::DeviceSetupStatusAsync; return Device::DeviceSetupStatusAsync;
}
if (device->deviceClassId() == heosDeviceClassId) {
qCDebug(dcDenon) << "Setup Denon device" << device->paramValue(heosDeviceIpParamTypeId).toString();
QHostAddress address(device->paramValue(heosDeviceIpParamTypeId).toString());
Heos *heos = new Heos(address, this);
connect(heos, &Heos::connectionStatusChanged, this, &DevicePluginDenon::onHeosConnectionChanged);
connect(heos, &Heos::playerDiscovered, this, &DevicePluginDenon::onHeosPlayerDiscovered);
connect(heos, &Heos::playStateReceived, this, &DevicePluginDenon::onHeosPlayStateReceived);
connect(heos, &Heos::repeatModeReceived, this, &DevicePluginDenon::onHeosRepeatModeReceived);
connect(heos, &Heos::shuffleModeReceived, this, &DevicePluginDenon::onHeosShuffleModeReceived);
connect(heos, &Heos::muteStatusReceived, this, &DevicePluginDenon::onHeosMuteStatusReceived);
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;
}
if (device->deviceClassId() == heosPlayerDeviceClassId) {
return Device::DeviceSetupStatusSuccess;
}
return Device::DeviceSetupStatusFailure;
} }
void DevicePluginDenon::deviceRemoved(Device *device) void DevicePluginDenon::deviceRemoved(Device *device)
{ {
qCDebug(dcDenon) << "Delete " << device->name(); qCDebug(dcDenon) << "Delete " << device->name();
if (m_denonConnection.isNull()){
qCWarning(dcDenon) << "Invalid connection pointer" << device->id().toString(); if (device->deviceClassId() == AVRX1000DeviceClassId) {
return; AvrConnection *denonConnection = m_avrConnections.value(device);
m_avrConnections.remove(device);
denonConnection->disconnectDevice();
denonConnection->deleteLater();
}
if (device->deviceClassId() == heosDeviceClassId) {
if (m_avrConnections.contains(device)) {
AvrConnection *denonConnection = m_avrConnections.value(device);
m_avrConnections.remove(device);
denonConnection->disconnectDevice();
denonConnection->deleteLater();
}
}
if (myDevices().empty()) {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
} }
m_device.clear();
m_denonConnection->disconnectDenon();
m_denonConnection->deleteLater();
} }
Device::DeviceError DevicePluginDenon::executeAction(Device *device, const Action &action) Device::DeviceError DevicePluginDenon::executeAction(Device *device, const Action &action)
{ {
qCDebug(dcDenon) << "Execute action" << device->id() << action.id() << action.params(); qCDebug(dcDenon) << "Execute action" << device->id() << action.id() << action.params();
if (device->deviceClassId() == AVRX1000DeviceClassId) { if (device->deviceClassId() == AVRX1000DeviceClassId) {
AvrConnection *avrConnection = m_avrConnections.value(device);
// check connection state
if (m_denonConnection.isNull() || !m_denonConnection->connected())
return Device::DeviceErrorHardwareNotAvailable;
// check if the requested action is our "update" action ...
if (action.actionTypeId() == AVRX1000PowerActionTypeId) { if (action.actionTypeId() == AVRX1000PowerActionTypeId) {
// Print information that we are executing now the update action bool power = action.param(AVRX1000PowerActionPowerParamTypeId).value().toBool();
qCDebug(dcDenon) << "set power action" << action.id(); avrConnection->setPower(power);
qCDebug(dcDenon) << "power: " << action.param(AVRX1000PowerActionPowerParamTypeId).value().Bool;
if (action.param(AVRX1000PowerActionPowerParamTypeId).value().toBool() == true){
QByteArray cmd = "PWON\r";
qCDebug(dcDenon) << "Execute power: " << action.id() << cmd;
m_denonConnection->sendData(cmd);
} else {
QByteArray cmd = "PWSTANDBY\r";
qCDebug(dcDenon) << "Execute power: " << action.id() << cmd;
m_denonConnection->sendData(cmd);
}
return Device::DeviceErrorNoError; return Device::DeviceErrorNoError;
} else if (action.actionTypeId() == AVRX1000VolumeActionTypeId) { } else if (action.actionTypeId() == AVRX1000VolumeActionTypeId) {
QByteArray vol = action.param(AVRX1000VolumeActionVolumeParamTypeId).value().toByteArray(); int vol = action.param(AVRX1000VolumeActionVolumeParamTypeId).value().toInt();
QByteArray cmd = "MV" + vol + "\r"; avrConnection->setVolume(vol);
qCDebug(dcDenon) << "Execute volume" << action.id() << cmd;
m_denonConnection->sendData(cmd);
return Device::DeviceErrorNoError; return Device::DeviceErrorNoError;
} else if (action.actionTypeId() == AVRX1000ChannelActionTypeId) { } else if (action.actionTypeId() == AVRX1000ChannelActionTypeId) {
qCDebug(dcDenon) << "Execute update action" << action.id(); qCDebug(dcDenon) << "Execute update action" << action.id();
QByteArray channel = action.param(AVRX1000ChannelActionChannelParamTypeId).value().toByteArray(); QByteArray channel = action.param(AVRX1000ChannelActionChannelParamTypeId).value().toByteArray();
QByteArray cmd = "SI" + channel + "\r"; avrConnection->setChannel(channel);
return Device::DeviceErrorNoError;
qCDebug(dcDenon) << "Change to channel:" << cmd; } else if (action.actionTypeId() == AVRX1000IncreaseVolumeActionTypeId) {
m_denonConnection->sendData(cmd);
avrConnection->increaseVolume();
return Device::DeviceErrorNoError;
} else if (action.actionTypeId() == AVRX1000DecreaseVolumeActionTypeId) {
avrConnection->decreaseVolume();
return Device::DeviceErrorNoError;
} else if (action.actionTypeId() == AVRX1000SurroundModeActionTypeId) {
QByteArray surroundMode = action.param(AVRX1000SurroundModeActionSurroundModeParamTypeId).value().toByteArray();
avrConnection->setSurroundMode(surroundMode);
return Device::DeviceErrorNoError;
}
return Device::DeviceErrorActionTypeNotFound;
}
if (device->deviceClassId() == heosPlayerDeviceClassId) {
Device *heosDevice = myDevices().findById(device->parentId());
Heos *heos = m_heos.value(heosDevice);
int playerId = device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt();
if (action.actionTypeId() == heosPlayerVolumeActionTypeId) {
int volume = action.param(heosPlayerVolumeActionVolumeParamTypeId).value().toInt();
heos->setVolume(playerId, volume);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerMuteActionTypeId) {
bool mute = action.param(heosPlayerMuteActionMuteParamTypeId).value().toBool();
heos->setMute(playerId, mute);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPlaybackStatusActionTypeId) {
QString playbackStatus = action.param(heosPlayerPlaybackStatusActionPlaybackStatusParamTypeId).value().toString();
if (playbackStatus == "playing") {
heos->setPlayerState(playerId, PLAYER_STATE_PLAY);
} else if (playbackStatus == "stopping") {
heos->setPlayerState(playerId, PLAYER_STATE_STOP);
} else if (playbackStatus == "pausing") {
heos->setPlayerState(playerId, PLAYER_STATE_PAUSE);
}
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerShuffleActionTypeId) {
bool shuffle = action.param(heosPlayerShuffleActionShuffleParamTypeId).value().toBool();
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;
}
if (action.actionTypeId() == heosPlayerSkipBackActionTypeId) {
heos->playPrevious(playerId);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerStopActionTypeId) {
heos->setPlayerState(playerId, PLAYER_STATE_STOP);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPlayActionTypeId) {
heos->setPlayerState(playerId, PLAYER_STATE_PLAY);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPauseActionTypeId) {
heos->setPlayerState(playerId, PLAYER_STATE_PAUSE);
return Device::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerSkipNextActionTypeId) {
heos->playNext(playerId);
return Device::DeviceErrorNoError; return Device::DeviceErrorNoError;
} }
return Device::DeviceErrorActionTypeNotFound; return Device::DeviceErrorActionTypeNotFound;
@ -134,125 +313,407 @@ Device::DeviceError DevicePluginDenon::executeAction(Device *device, const Actio
return Device::DeviceErrorDeviceClassNotFound; return Device::DeviceErrorDeviceClassNotFound;
} }
void DevicePluginDenon::postSetupDevice(Device *device)
{
if (device->deviceClassId() == heosDeviceClassId) {
Heos *heos = m_heos.value(device);
heos->getPlayers();
}
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();
heos->getPlayerState(playerId);
heos->getPlayMode(playerId);
heos->getVolume(playerId);
heos->getMute(playerId);
heos->getNowPlayingMedia(playerId);
}
}
void DevicePluginDenon::onPluginTimer() void DevicePluginDenon::onPluginTimer()
{ {
if (m_denonConnection.isNull()) foreach(AvrConnection *denonConnection, m_avrConnections.values()) {
return; if (!denonConnection->connected()) {
denonConnection->connectDevice();
}
Device *device = m_avrConnections.key(denonConnection);
if (device->deviceClassId() == AVRX1000DeviceClassId) {
denonConnection->getAllStatus();
}
}
if (!m_denonConnection->connected()) { foreach(Device *device, myDevices()) {
m_denonConnection->connectDenon();
} else { if (device->deviceClassId() == heosDeviceClassId) {
m_denonConnection->sendData("PW?\rSI?\rMV?\r"); Heos *heos = m_heos.value(device);
heos->getPlayers();
heos->registerForChangeEvents(true);
}
if (device->deviceClassId() == heosPlayerDeviceClassId) {
Device *heosDevice = myDevices().findById(device->parentId());
Heos *heos = m_heos.value(heosDevice);
int playerId = device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt();
heos->getPlayerState(playerId);
heos->getPlayMode(playerId);
heos->getVolume(playerId);
heos->getMute(playerId);
heos->getNowPlayingMedia(playerId);
}
} }
} }
void DevicePluginDenon::onConnectionChanged() void DevicePluginDenon::onAvrConnectionChanged(bool status)
{ {
if (!m_device) AvrConnection *denonConnection = static_cast<AvrConnection *>(sender());
Device *device = m_avrConnections.key(denonConnection);
if (!device)
return; return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
// if the device is connected // if the device is connected
if (m_denonConnection->connected()) { if (status) {
// and from the first setup // and from the first setup
if (m_asyncSetups.contains(m_denonConnection)) { if (m_asyncAvrSetups.contains(denonConnection)) {
m_asyncSetups.removeAll(m_denonConnection); m_asyncAvrSetups.removeAll(denonConnection);
m_denonConnection->sendData("PW?\rSI?\rMV?\r");
emit deviceSetupFinished(m_device, Device::DeviceSetupStatusSuccess); emit deviceSetupFinished(device, Device::DeviceSetupStatusSuccess);
} }
} }
device->setStateValue(AVRX1000ConnectedStateTypeId, denonConnection->connected());
// Set connection status
m_device->setStateValue(AVRX1000ConnectedStateTypeId, m_denonConnection->connected());
}
void DevicePluginDenon::onDataReceived(const QByteArray &data)
{
qCDebug(dcDenon) << "Data received" << data;
// if there is no device, return
if (m_device.isNull())
return;
if (data.contains("MV") && !data.contains("MAX")){
int index = data.indexOf("MV");
int vol = data.mid(index+2, 2).toInt();
qCDebug(dcDenon) << "Update volume:" << vol;
m_device->setStateValue(AVRX1000VolumeStateTypeId, vol);
}
if (data.contains("SI")) {
QString 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) << "Update channel:" << cmd;
m_device->setStateValue(AVRX1000ChannelStateTypeId, cmd);
}
if (data.contains("PWON")) {
qCDebug(dcDenon) << "Update power on";
m_device->setStateValue(AVRX1000PowerStateTypeId, true);
} else if (data.contains("PWSTANDBY")) {
qCDebug(dcDenon) << "Update power off";
m_device->setStateValue(AVRX1000PowerStateTypeId, false);
} }
} }
void DevicePluginDenon::onSocketError() void DevicePluginDenon::onAvrVolumeChanged(int volume)
{ {
// if there is no device, return AvrConnection *denonConnection = static_cast<AvrConnection *>(sender());
if (m_device.isNull()) Device *device = m_avrConnections.key(denonConnection);
if (!device)
return; return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
device->setStateValue(AVRX1000VolumeStateTypeId, volume);
}
}
void DevicePluginDenon::onAvrChannelChanged(const QByteArray &channel)
{
AvrConnection *denonConnection = static_cast<AvrConnection *>(sender());
Device *device = m_avrConnections.key(denonConnection);
if (!device)
return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
device->setStateValue(AVRX1000ChannelStateTypeId, channel);
}
}
void DevicePluginDenon::onAvrMuteChanged(bool mute)
{
AvrConnection *denonConnection = static_cast<AvrConnection *>(sender());
Device *device = m_avrConnections.key(denonConnection);
if (!device)
return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
device->setStateValue(AVRX1000MuteStateTypeId, mute);
}
}
void DevicePluginDenon::onAvrPowerChanged(bool power)
{
AvrConnection *denonConnection = static_cast<AvrConnection *>(sender());
Device *device = m_avrConnections.key(denonConnection);
if (!device)
return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
device->setStateValue(AVRX1000PowerStateTypeId, power);
}
}
void DevicePluginDenon::onAvrSurroundModeChanged(const QByteArray &surroundMode)
{
AvrConnection *denonConnection = static_cast<AvrConnection *>(sender());
Device *device = m_avrConnections.key(denonConnection);
if (!device)
return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
device->setStateValue(AVRX1000SurroundModeStateTypeId, surroundMode);
}
}
void DevicePluginDenon::onAvrSocketError()
{
AvrConnection *denonConnection = static_cast<AvrConnection *>(sender());
Device *device = m_avrConnections.key(denonConnection);
if (!device)
return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
// Check if setup running for this device // Check if setup running for this device
if (m_asyncSetups.contains(m_denonConnection)) { if (m_asyncAvrSetups.contains(denonConnection)) {
m_asyncAvrSetups.removeAll(denonConnection);
qCWarning(dcDenon()) << "Could not add device. The setup failed."; qCWarning(dcDenon()) << "Could not add device. The setup failed.";
emit deviceSetupFinished(m_device, Device::DeviceSetupStatusFailure); emit deviceSetupFinished(device, Device::DeviceSetupStatusFailure);
// Delete the connection, the device will not be added and // Delete the connection, the device will not be added and
// the connection will be created in the next setup // the connection will be created in the next setup
m_denonConnection->deleteLater(); denonConnection->deleteLater();
m_avrConnections.remove(device);
}
}
}
void DevicePluginDenon::onUpnpDiscoveryFinished()
{
qCDebug(dcDenon()) << "Upnp discovery finished";
UpnpDiscoveryReply *reply = static_cast<UpnpDiscoveryReply *>(sender());
if (reply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) {
qCWarning(dcDenon()) << "Upnp discovery error" << reply->error();
}
reply->deleteLater();
if (reply->deviceDescriptors().isEmpty()) {
qCDebug(dcDenon) << "No UPnP device found.";
return;
}
QList<DeviceDescriptor> heosDescriptors;
foreach (const UpnpDeviceDescriptor &upnpDevice, reply->deviceDescriptors()) {
if (upnpDevice.modelName().contains("HEOS")) {
QString serialNumber = upnpDevice.serialNumber();
if (serialNumber != "0000001") {
// child devices have serial number 0000001
qCDebug(dcDenon) << "UPnP device found:" << upnpDevice.modelDescription() << upnpDevice.friendlyName() << upnpDevice.hostAddress().toString() << upnpDevice.modelName() << upnpDevice.manufacturer() << upnpDevice.serialNumber();
DeviceDescriptor descriptor(heosDeviceClassId, upnpDevice.modelName(), serialNumber);
ParamList params;
foreach (Device *existingDevice, myDevices()) {
if (existingDevice->paramValue(heosDeviceSerialNumberParamTypeId).toString().contains(serialNumber, Qt::CaseSensitivity::CaseInsensitive)) {
descriptor.setDeviceId(existingDevice->id());
break;
}
}
params.append(Param(heosDeviceModelNameParamTypeId, upnpDevice.modelName()));
params.append(Param(heosDeviceIpParamTypeId, upnpDevice.hostAddress().toString()));
params.append(Param(heosDeviceSerialNumberParamTypeId, serialNumber));
descriptor.setParams(params);
heosDescriptors.append(descriptor);
}
}
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(bool status)
{
Heos *heos = static_cast<Heos *>(sender());
heos->registerForChangeEvents(true);
Device *device = m_heos.key(heos);
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);
}
}
}
}
}
void DevicePluginDenon::onHeosPlayerDiscovered(HeosPlayer *heosPlayer) {
Heos *heos = static_cast<Heos *>(sender());
Device *device = m_heos.key(heos);
foreach (Device *heosPlayerDevice, myDevices()) {
if(heosPlayerDevice->deviceClassId() == heosPlayerDeviceClassId) {
if (heosPlayerDevice->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt() == heosPlayer->playerId())
return;
}
}
QList<DeviceDescriptor> heosPlayerDescriptors;
DeviceDescriptor descriptor(heosPlayerDeviceClassId, heosPlayer->name(), heosPlayer->playerModel(), device->id());
ParamList params;
params.append(Param(heosPlayerDeviceModelParamTypeId, heosPlayer->playerModel()));
params.append(Param(heosPlayerDevicePlayerIdParamTypeId, heosPlayer->playerId()));
params.append(Param(heosPlayerDeviceSerialNumberParamTypeId, heosPlayer->serialNumber()));
params.append(Param(heosPlayerDeviceVersionParamTypeId, heosPlayer->playerVersion()));
descriptor.setParams(params);
qCDebug(dcDenon) << "Found new heos player" << heosPlayer->name();
heosPlayerDescriptors.append(descriptor);
autoDevicesAppeared(heosPlayerDeviceClassId, heosPlayerDescriptors);
}
void DevicePluginDenon::onHeosPlayStateReceived(int playerId, PLAYER_STATE state)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
if (state == PLAYER_STATE_PAUSE) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Paused");
} else if (state == PLAYER_STATE_PLAY) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Playing");
} else if (state == PLAYER_STATE_STOP) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Stopped");
}
break;
} }
} }
void DevicePluginDenon::onHeosRepeatModeReceived(int playerId, REPEAT_MODE repeatMode)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
if (repeatMode == REPEAT_MODE_ALL) {
device->setStateValue(heosPlayerRepeatStateTypeId, "All");
} else if (repeatMode == REPEAT_MODE_ONE) {
device->setStateValue(heosPlayerRepeatStateTypeId, "One");
} else if (repeatMode == REPEAT_MODE_OFF) {
device->setStateValue(heosPlayerRepeatStateTypeId, "None");
}
break;
}
}
void DevicePluginDenon::onHeosShuffleModeReceived(int playerId, bool shuffle)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
device->setStateValue(heosPlayerMuteStateTypeId, shuffle);
break;
}
}
void DevicePluginDenon::onHeosMuteStatusReceived(int playerId, bool mute)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
device->setStateValue(heosPlayerMuteStateTypeId, mute);
break;
}
}
void DevicePluginDenon::onHeosVolumeStatusReceived(int playerId, int volume)
{
foreach(Device *device, myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId)) {
device->setStateValue(heosPlayerVolumeStateTypeId, volume);
break;
}
}
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;
}
}
void DevicePluginDenon::onAvahiServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry)
{
qCDebug(dcDenon()) << "Avahi service entry added:" << serviceEntry;
}
void DevicePluginDenon::onAvahiServiceEntryRemoved(const ZeroConfServiceEntry &serviceEntry)
{
qCDebug(dcDenon()) << "Avahi service entry removed:" << serviceEntry;
}

View File

@ -1,7 +1,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> * * Copyright (C) 2015 Simon Stürz <simon.stuerz@nymea.io> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> * * Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* * * *
* This file is part of nymea. * * This file is part of nymea. *
* * * *
@ -24,18 +24,21 @@
#ifndef DEVICEPLUGINDENON_H #ifndef DEVICEPLUGINDENON_H
#define DEVICEPLUGINDENON_H #define DEVICEPLUGINDENON_H
#include "heos.h"
#include "avrconnection.h"
#include "plugintimer.h"
#include "devices/deviceplugin.h" #include "devices/deviceplugin.h"
#include "network/zeroconf/zeroconfservicebrowser.h"
#include "network/zeroconf/zeroconfserviceentry.h"
#include <QProcess>
#include <QPair>
#include <QHash> #include <QHash>
#include <QObject> #include <QObject>
#include <QPointer> #include <QPointer>
#include <QHostAddress> #include <QHostAddress>
#include <QNetworkReply> #include <QNetworkReply>
#include "plugintimer.h"
#include "denonconnection.h"
class DevicePluginDenon : public DevicePlugin class DevicePluginDenon : public DevicePlugin
{ {
Q_OBJECT Q_OBJECT
@ -45,27 +48,50 @@ class DevicePluginDenon : public DevicePlugin
public: public:
explicit DevicePluginDenon(); explicit DevicePluginDenon();
~DevicePluginDenon();
void init() override; Device::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params) override;
Device::DeviceSetupStatus setupDevice(Device *device) override; Device::DeviceSetupStatus setupDevice(Device *device) override;
void deviceRemoved(Device *device) override; void postSetupDevice(Device * device) override;
Device::DeviceError executeAction(Device *device, const Action &action) override; Device::DeviceError executeAction(Device *device, const Action &action) override;
void deviceRemoved(Device *device) override;
private: private:
PluginTimer *m_pluginTimer = nullptr; PluginTimer *m_pluginTimer = nullptr;
QPointer<Device> m_device; ZeroConfServiceBrowser *m_serviceBrowser = nullptr;
QPointer<DenonConnection> m_denonConnection;
QList<DenonConnection *> m_asyncSetups; QHash<Device *, AvrConnection*> m_avrConnections;
QHash<Device *, Heos*> m_heos;
QList<AvrConnection *> m_asyncAvrSetups;
QList<Heos *> m_asyncHeosSetups;
QHash<int, Device *> m_playerIds;
QHash<int, Device *> m_discoveredPlayerIds;
QHash<const Action *, int> m_asyncActions;
QHash <ActionId, Device *> m_asyncActions;
QHash <QNetworkReply *, ActionId> m_asyncActionReplies;
private slots: private slots:
void onPluginTimer(); void onPluginTimer();
void onConnectionChanged(); void onUpnpDiscoveryFinished();
void onDataReceived(const QByteArray &data);
void onSocketError(); void onHeosConnectionChanged(bool status);
void onHeosPlayerDiscovered(HeosPlayer *heosPlayer);
void onHeosPlayStateReceived(int playerId, PLAYER_STATE state);
void onHeosShuffleModeReceived(int playerId, bool shuffle);
void onHeosRepeatModeReceived(int playerId, REPEAT_MODE repeatMode);
void onHeosMuteStatusReceived(int playerId, bool mute);
void onHeosVolumeStatusReceived(int playerId, int volume);
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);
void onAvrConnectionChanged(bool status);
void onAvrSocketError();
void onAvrVolumeChanged(int volume);
void onAvrChannelChanged(const QByteArray &channel);
void onAvrMuteChanged(bool mute);
void onAvrPowerChanged(bool power);
void onAvrSurroundModeChanged(const QByteArray &surroundMode);
}; };
#endif // DEVICEPLUGINDENON_H #endif // DEVICEPLUGINDENON_H

View File

@ -12,19 +12,26 @@
"id": "1cd3d67e-aba0-450e-9e2a-483a1527aba6", "id": "1cd3d67e-aba0-450e-9e2a-483a1527aba6",
"name": "AVRX1000", "name": "AVRX1000",
"displayName": "AVR X1000", "displayName": "AVR X1000",
"createMethods": ["user"], "createMethods": ["discovery"],
"interfaces": ["extendedvolumecontroller", "connectable", "power"],
"paramTypes": [ "paramTypes": [
{ {
"id": "a54b98b4-b78f-41dd-a257-14425c6cf9ab", "id": "cb6eeeb0-3d75-43b6-8177-b5ac19648557",
"name": "ip", "name": "ip",
"displayName": "ip", "displayName": "IPv4 address",
"type" : "QString", "type" : "QString",
"inputType": "IPv4Address" "inputType": "IPv4Address"
},
{
"id": "2e8806cb-f6f3-4e9a-b6ea-0b35f75e61c5",
"name": "id",
"displayName": "ID",
"type" : "QString"
} }
], ],
"stateTypes": [ "stateTypes": [
{ {
"id": "4d1790bf-28c6-4c1f-8892-ba1a0ef140f5", "id": "fc1dee8b-8fcc-4ec2-8fe6-6be4f5f47a5c",
"name": "connected", "name": "connected",
"displayName": "connected", "displayName": "connected",
"displayNameEvent": "connected changed", "displayNameEvent": "connected changed",
@ -42,23 +49,32 @@
"writable": true "writable": true
}, },
{ {
"displayName": "volume", "displayName": "Mute",
"id": "773636b9-304d-463a-8755-fc7488dc0ff3", "id": "3e11470d-a5b7-499c-be55-9b1b4fe5eedf",
"name": "volume", "name": "mute",
"displayNameEvent": "volume changed", "displayNameEvent": "Mute changed",
"displayNameAction": "Set volume", "displayNameAction": "Set mute",
"type": "int", "type": "bool",
"unit": "Dezibel", "defaultValue": false,
"defaultValue": 0,
"minValue": 0,
"maxValue": 80,
"writable": true "writable": true
}, },
{ {
"displayName": "channel", "displayName": "Volume",
"id": "773636b9-304d-463a-8755-fc7488dc0ff3",
"name": "volume",
"displayNameEvent": "Volume changed",
"displayNameAction": "Set volume",
"type": "int",
"defaultValue": 0,
"minValue": 0,
"maxValue": 100,
"writable": true
},
{
"displayName": "Channel",
"id": "f29ffa2c-31d6-4d88-b160-a38288c82ce1", "id": "f29ffa2c-31d6-4d88-b160-a38288c82ce1",
"name": "channel", "name": "channel",
"displayNameEvent": "channel changed", "displayNameEvent": "Channel changed",
"displayNameAction": "Set channel", "displayNameAction": "Set channel",
"type": "QString", "type": "QString",
"writable": true, "writable": true,
@ -84,6 +100,279 @@
"FVP" "FVP"
], ],
"defaultValue": "TUNER" "defaultValue": "TUNER"
},
{
"displayName": "Surround mode",
"id": "4f203bdd-691c-4384-a934-2d49a5448f0a",
"name": "surroundMode",
"displayNameEvent": "Surround mode changed",
"displayNameAction": "Set surround mode",
"type": "QString",
"writable": true,
"possibleValues": [
"MOVIE",
"MUSIC",
"GAME",
"PURE DIRECT",
"DIRECT",
"STEREO",
"STANDARD",
"DOLBY DIGITAL",
"DTS SUROUND",
"MCH STEREO",
"ROCK ARENA",
"JAZZ CLUB",
"MONO MOVIE",
"MATRIX"
],
"defaultValue": "MOVIE"
}
],
"actionTypes": [
{
"id": "4ae686d6-2307-40a0-bd38-2cd3a92342cc",
"displayName": "Increase volume",
"name": "increaseVolume",
"paramTypes": [
{
"id": "765c7e2a-9eb6-46fc-a880-4e96c81f8d1e",
"displayName": "Step",
"name": "step",
"type": "int"
}
]
},
{
"id": "d3752c32-92e3-4396-8e2f-ab5e57c6cfb1",
"displayName": "Decrease volume",
"name": "decreaseVolume",
"paramTypes": [
{
"id": "1d54fda8-336c-436f-ab2b-e8bd549f830c",
"displayName": "Step",
"name": "step",
"type": "int"
}
]
}
]
},
{
"id": "28bbf4c6-dfd8-4d9d-aa27-5daf2c25d63c",
"name": "heos",
"displayName": "Heos",
"createMethods": ["discovery"],
"interfaces": ["gateway"],
"paramTypes": [
{
"id": "a54b98b4-b78f-41dd-a257-14425c6cf9ab",
"name": "ip",
"displayName": "IPv4 address",
"type" : "QString",
"inputType": "IPv4Address"
},
{
"id": "f796664d-6cb7-4f29-9d05-771968d82a32",
"name": "serialNumber",
"displayName": "Serial number",
"type" : "QString"
},
{
"id": "ab1a0be8-e3a5-4f95-b9b7-893de1ca4cf7",
"name": "modelName",
"displayName": "Model name",
"type" : "QString"
}
],
"stateTypes": [
{
"id": "4d1790bf-28c6-4c1f-8892-ba1a0ef140f5",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"defaultValue": false,
"type": "bool"
}
]
},
{
"id": "fce5247f-4c6d-408f-ac62-e5973dc6adfa",
"name": "heosPlayer",
"displayName": "Heos player",
"createMethods": ["auto"],
"interfaces": ["mediaplayer", "mediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"],
"paramTypes":[
{
"id": "89629008-6ad8-4e92-863d-b86e0e012d0b",
"name": "playerId",
"displayName": "Player ID",
"type" : "int"
},
{
"id": "e760f92b-8fca-4f20-aead-a52045505b81",
"name": "model",
"displayName": "Model",
"type" : "QString"
},
{
"id": "aa1158f7-b451-456a-840f-4f0c63b2b7f0",
"name": "version",
"displayName": "Version",
"type" : "QString"
},
{
"id": "866e8d6a-953f-4bdc-8d85-8d92e51e8592",
"name": "serialNumber",
"displayName": "Serial number",
"type" : "QString"
}
],
"stateTypes": [
{
"id": "9a4e527e-057c-4b19-8a02-605cc8349f5e",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "fcc89c7c-b793-4b6f-a3dc-0e0e3a86748f",
"name": "mute",
"displayName": "Mute",
"displayNameEvent": "Mute changed",
"displayNameAction": "Set mute",
"type": "bool",
"defaultValue": false,
"cached": false,
"writable": true
},
{
"id": "6d4886a1-fa5d-4889-96c5-7a1c206f59be",
"name": "volume",
"displayName": "Volume",
"displayNameEvent": "Volume changed",
"displayNameAction": "Set volume",
"type": "int",
"defaultValue": 50,
"minValue": 0,
"maxValue": 100,
"writable": true
},
{
"id": "6db3b484-4cd4-477b-b822-275865d308db",
"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": "4b581237-acf5-4d8f-9e83-9b24e9ac900a",
"name": "shuffle",
"displayName": "Shuffle",
"displayNameEvent": "Shuffle changed",
"displayNameAction": "Set shuffle",
"type": "bool",
"defaultValue": false,
"cached": false,
"writable": true
},
{
"id": "4e60cd17-5845-4351-aa2c-2504610e1532",
"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
},
{
"id": "eee22722-3ee5-48f7-8af8-275dc04b21eb",
"name": "source",
"displayName": "Source",
"displayNameEvent": "Source changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "0a9183a4-b633-4773-ba7a-f4266895157e",
"name": "artist",
"displayName": "Artist",
"displayNameEvent": "Artist changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "9cd60864-f141-4e03-a85b-357690cad1b8",
"name": "collection",
"displayName": "Album",
"displayNameEvent": "Album changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "bbeecf30-6feb-48d5-ade3-57b2a4eea05f",
"name": "title",
"displayName": "Title",
"displayNameEvent": "Title changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "a7f0ba95-383a-4efd-adc5-a36e50a04018",
"name": "artwork",
"displayName": "Artwork",
"displayNameEvent": "Artwork changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "c59835ac-ee6e-4e6c-aa20-aeb3501937c5",
"name": "playerType",
"displayName": "Player type",
"displayNameEvent": "Player type changed",
"possibleValues": [
"audio",
"video"
],
"type": "QString",
"defaultValue": "audio"
}
],
"actionTypes": [
{
"id": "a718f7e9-0b54-4403-b661-49f7b0d13085",
"name": "skipBack",
"displayName": "Akip back"
},
{
"id": "c4b29c09-e3b3-4843-b6d9-e032f3fc1d78",
"name": "stop",
"displayName": "Stop"
},
{
"id": "c64964e4-cea0-468a-a9bf-8f69657b74e9",
"name": "play",
"displayName": "Play"
},
{
"id": "21c1cbe6-278f-4688-a65f-6620be1ee5ea",
"name": "pause",
"displayName": "Pause"
},
{
"id": "57697e9c-ce5e-4b8f-b42e-16662829ceb2",
"name": "skipNext",
"displayName": "Skip next"
} }
] ]
} }

588
denon/heos.cpp Normal file
View File

@ -0,0 +1,588 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@guh.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "heos.h"
#include "extern-plugininfo.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QUrlQuery>
#include <QTimer>
Heos::Heos(const QHostAddress &hostAddress, QObject *parent) :
QObject(parent),
m_hostAddress(hostAddress)
{
m_socket = new QTcpSocket(this);
connect(m_socket, &QTcpSocket::connected, this, &Heos::onConnected);
connect(m_socket, &QTcpSocket::disconnected, this, &Heos::onDisconnected);
connect(m_socket, &QTcpSocket::readyRead, this, &Heos::readData);
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
}
Heos::~Heos()
{
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;
if (state) {
query = "?enable=on";
} else {
query = "?enable=off";
}
QByteArray cmd = "heos://system/register_for_change_events" + query + "\r\n";
qCDebug(dcDenon) << "Register for change events:" << cmd;
m_socket->write(cmd);
}
void Heos::sendHeartbeat()
{
QByteArray cmd = "heos://system/heart_beat\r\n";
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";
m_socket->write(cmd);
}
void Heos::setVolume(int playerId, int volume)
{
QByteArray cmd = "heos://player/set_volume?pid=" + QVariant(playerId).toByteArray() + "&level=" + QVariant(volume).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Set volume:" << cmd;
m_socket->write(cmd);
}
void Heos::getMute(int playerId)
{
QByteArray cmd = "heos://player/get_mute?pid=" + QVariant(playerId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
void Heos::setMute(int playerId, bool state)
{
QByteArray stateQuery;
if(state) {
stateQuery = "&state=on";
} else {
stateQuery = "&state=off";
}
QByteArray cmd = "heos://player/set_mute?pid=" + QVariant(playerId).toByteArray() + stateQuery + "\r\n";
qCDebug(dcDenon) << "Set mute:" << cmd;
m_socket->write(cmd);
}
void Heos::setPlayerState(int playerId, PLAYER_STATE state)
{
QByteArray playerStateQuery;
if (state == PLAYER_STATE_PLAY){
playerStateQuery = "&state=play";
} else if (state == PLAYER_STATE_PAUSE){
playerStateQuery = "&state=pause";
} else if (state == PLAYER_STATE_STOP){
playerStateQuery = "&state=stop";
}
QByteArray cmd = "heos://player/set_play_state?pid=" + QVariant(playerId).toByteArray() + playerStateQuery + "\r\n";
qCDebug(dcDenon) << "Set play mode:" << cmd;
m_socket->write(cmd);
}
void Heos::getPlayerState(int playerId)
{
QByteArray cmd = "heos://player/get_play_state?pid=" + QVariant(playerId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
void Heos::setPlayMode(int playerId, REPEAT_MODE repeatMode, bool shuffle)
{
QByteArray repeatModeQuery;
if (repeatMode == REPEAT_MODE_OFF) {
repeatModeQuery = "&repeat=off";
} else if (repeatMode == REPEAT_MODE_ONE) {
repeatModeQuery = "&repeat=on_one";
} else if (repeatMode == REPEAT_MODE_ALL) {
repeatModeQuery = "&repeat=on_all";
}
QByteArray shuffleQuery;
if (shuffle) {
shuffleQuery = "&shuffle=on";
} else {
shuffleQuery = "&shuffle=off";
}
QByteArray cmd = "heos://player/set_play_mode?pid=" + QVariant(playerId).toByteArray() + repeatModeQuery + shuffleQuery + "\r\n";
qCDebug(dcDenon) << "Set play mode:" << cmd;
m_socket->write(cmd);
}
void Heos::getPlayMode(int playerId)
{
QByteArray cmd = "heos://player/get_play_mode?pid=" + QVariant(playerId).toByteArray() + "\r\n";
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";
qCDebug(dcDenon) << "Play next:" << cmd;
m_socket->write(cmd);
}
void Heos::playPrevious(int playerId)
{
QByteArray cmd = "heos://player/play_previous?pid=" + QVariant(playerId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "Play previous:" << cmd;
m_socket->write(cmd);
}
void Heos::volumeUp(int playerId, int step)
{
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);
}
void Heos::volumeDown(int playerId, int step)
{
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);
}
void Heos::clearQueue(int playerId)
{
QByteArray cmd = "heos://player/clear_queue?pid=" + QVariant(playerId).toByteArray() + "\r\n";
qCDebug(dcDenon) << "clear queue:" << cmd;
m_socket->write(cmd);
}
void Heos::moveQueue(int playerId, int sourcQueueId, int destinationQueueId)
{
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->write(cmd);
}
void Heos::toggleGroupMute(int groupId)
{
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();
emit connectionStatusChanged(true);
}
void Heos::onDisconnected()
{
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)
{
qCWarning(dcDenon) << "socket error:" << socketError << m_socket->errorString();
}
void Heos::readData()
{
int playerId = 0;
QByteArray data;
QJsonParseError error;
while (m_socket->canReadLine()) {
data = m_socket->readLine();
//qDebug(dcDenon) << data;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcDenon) << "failed to parse json :" << error.errorString();
return;
}
QVariantMap dataMap = jsonDoc.toVariant().toMap();
if (dataMap.contains("heos")) {
QString command = dataMap.value("heos").toMap().value("command").toString();
if (command.contains("register_for_change_events")) {
QString enabled = dataMap.value("heos").toMap().value("message").toString();
if (enabled.contains("off")) {
qDebug(dcDenon) << "Events are disabled";
m_eventRegistered = false;
} else {
qDebug(dcDenon) << "Events are enabled";
m_eventRegistered = true;
}
} else if (command.contains("get_players")) {
QVariantList payloadVariantList = jsonDoc.toVariant().toMap().value("payload").toList();
foreach (const QVariant &payloadEntryVariant, payloadVariantList) {
playerId = payloadEntryVariant.toMap().value("pid").toInt();
if(!m_heosPlayers.contains(playerId)){
QString serialNumber = payloadEntryVariant.toMap().value("serial").toString();
QString name = payloadEntryVariant.toMap().value("name").toString();
HeosPlayer *heosPlayer = new HeosPlayer(playerId, name, serialNumber, this);
m_heosPlayers.insert(playerId, heosPlayer);
emit playerDiscovered(heosPlayer);
}
}
} else {
QUrlQuery message(dataMap.value("heos").toMap().value("message").toString());
if (message.hasQueryItem("pid")) {
playerId = message.queryItemValue("pid").toInt();
}
if (command.contains("get_player_info")) {
//update heos player info
}
if (command.contains("get_now_playing_media")) {
QString artist = dataMap.value("payload").toMap().value("artist").toString();
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();
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")) {
PLAYER_STATE playState = PLAYER_STATE_STOP;
if (message.queryItemValue("state").contains("play")) {
playState = PLAYER_STATE_PLAY;
} else if (message.queryItemValue("state").contains("pause")) {
playState = PLAYER_STATE_PAUSE;
} else if (message.queryItemValue("state").contains("stop")) {
playState = PLAYER_STATE_STOP;
}
emit playStateReceived(playerId, playState);
}
}
if (command.contains("get_volume") || command.contains("set_volume")) {
if (message.hasQueryItem("level")) {
int volume = message.queryItemValue("level").toInt();
emit volumeStatusReceived(playerId, volume);
}
}
if (command.contains("get_mute") || command.contains("set_mute")) {
if (message.hasQueryItem("state")) {
QString state = message.queryItemValue("state");
if (state.contains("on")) {
emit muteStatusReceived(playerId, true);
} else {
emit muteStatusReceived(playerId, false);
}
}
}
if (command.contains("get_play_mode") || command.contains("set_play_mode")) {
if (message.hasQueryItem("shuffle") && message.hasQueryItem("repeat")) {
bool shuffle;
if (message.queryItemValue("shuffle").contains("on")){
shuffle = true;
} else {
shuffle = false;
}
emit shuffleModeReceived(playerId, shuffle);
REPEAT_MODE repeatMode = REPEAT_MODE_OFF;
if (message.queryItemValue("repeat").contains("on_all")){
repeatMode = REPEAT_MODE_ALL;
} else if (message.queryItemValue("repeat").contains("on_one")){
repeatMode = REPEAT_MODE_ONE;
} else if (message.queryItemValue("repeat").contains("off")){
repeatMode = REPEAT_MODE_OFF;
}
emit repeatModeReceived(playerId, repeatMode);
}
}
if (command.contains("player_state_changed")) {
if (message.hasQueryItem("state")) {
PLAYER_STATE playState = PLAYER_STATE_STOP;
if (message.queryItemValue("state").contains("play")) {
playState = PLAYER_STATE_PLAY;
} else if (message.queryItemValue("state").contains("pause")) {
playState = PLAYER_STATE_PAUSE;
} else if (message.queryItemValue("state").contains("stop")) {
playState = PLAYER_STATE_STOP;
}
emit playStateReceived(playerId, playState);
}
}
if (command.contains("player_volume_changed")) {
qDebug() << "Volume Changed";
if (message.hasQueryItem("level")) {
int volume = message.queryItemValue("level").toInt();
emit volumeStatusReceived(playerId, volume);
}
if (message.hasQueryItem("mute")) {
bool mute;
if (message.queryItemValue("mute").contains("on")) {
mute = true;
} else {
mute = false;
}
emit muteStatusReceived(playerId, mute);
}
}
if (command.contains("repeat_mode_changed")) {
if (message.hasQueryItem("repeat")) {
REPEAT_MODE repeatMode = REPEAT_MODE_OFF;
if (message.queryItemValue("repeat").contains("on_all")){
repeatMode = REPEAT_MODE_ALL;
} else if (message.queryItemValue("repeat").contains("on_one")){
repeatMode = REPEAT_MODE_ONE;
} else if (message.queryItemValue("repeat").contains("off")){
repeatMode = REPEAT_MODE_OFF;
}
emit repeatModeReceived(playerId, repeatMode);
}
}
if (command.contains("shuffle_mode_changed")) {
if (message.hasQueryItem("shuffle")) {
bool shuffle;
if (message.queryItemValue("shuffle").contains("on")){
shuffle = true;
} else {
shuffle = false;
}
emit shuffleModeReceived(playerId, shuffle);
}
}
if (command.contains("player_now_playing_changed")) {
getNowPlayingMedia(playerId);
}
}
}
}
}

124
denon/heos.h Normal file
View File

@ -0,0 +1,124 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef HEOS_H
#define HEOS_H
#include <QObject>
#include <QHostAddress>
#include <QTcpSocket>
#include "heosplayer.h"
#include "heostypes.h"
class Heos : public QObject
{
Q_OBJECT
public:
explicit Heos(const QHostAddress &hostAddress, QObject *parent = nullptr);
~Heos();
void connectHeos();
void setAddress(QHostAddress address);
QHostAddress getAddress();
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 getVolume(int playerId);
void getNowPlayingMedia(int playerId);
void getMute(int playerId);
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 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_eventRegistered = false;
QHostAddress m_hostAddress;
QTcpSocket *m_socket = nullptr;
QHash<int, HeosPlayer*> m_heosPlayers;
void setConnected(const bool &connected);
signals:
void playerDiscovered(HeosPlayer *heosPlayer);
void connectionStatusChanged(bool status);
void playStateReceived(int playerId, PLAYER_STATE state);
void shuffleModeReceived(int playerId, bool shuffle);
void repeatModeReceived(int playerId, REPEAT_MODE repeatMode);
void muteStatusReceived(int playerId, bool mute);
void volumeStatusReceived(int playerId, int volume);
void nowPlayingMediaStatusReceived(int playerId, SOURCE_ID source, QString artist, QString album, QString Song, QString artwork);
private slots:
void onConnected();
void onDisconnected();
void onError(QAbstractSocket::SocketError socketError);
void readData();
};
#endif // HEOS_H

95
denon/heosplayer.cpp Normal file
View File

@ -0,0 +1,95 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "heosplayer.h"
HeosPlayer::HeosPlayer(int playerId, QObject *parent) :
QObject(parent),
m_playerId(playerId)
{
}
HeosPlayer::HeosPlayer(int playerId, QString name, QString serialNumber, QObject *parent) :
QObject(parent),
m_playerId(playerId),
m_serialNumber(serialNumber),
m_name(name)
{
}
QString HeosPlayer::name()
{
return m_name;
}
void HeosPlayer::setName(QString name)
{
m_name = name;
}
int HeosPlayer::playerId()
{
return m_playerId;
}
int HeosPlayer::groupId()
{
return m_groupId;
}
void HeosPlayer::setGroupId(int groupId)
{
m_groupId = groupId;
}
QString HeosPlayer::playerModel()
{
return m_playerModel;
}
QString HeosPlayer::playerVersion()
{
return m_playerVersion;
}
QString HeosPlayer::network()
{
return m_network;
}
QString HeosPlayer::serialNumber()
{
return m_serialNumber;
}
QString HeosPlayer::lineOut()
{
return m_lineOut;
}
QString HeosPlayer::control()
{
return m_control;
}

60
denon/heosplayer.h Normal file
View File

@ -0,0 +1,60 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef HEOSPLAYER_H
#define HEOSPLAYER_H
#include <QObject>
class HeosPlayer : public QObject
{
Q_OBJECT
public:
explicit HeosPlayer(int playerId, QObject *parent = nullptr);
explicit HeosPlayer(int playerId, QString name, QString serialNumber, QObject *parent = nullptr);
QString name();
void setName(QString name);
int playerId();
int groupId();
void setGroupId(int groupId);
QString playerModel();
QString playerVersion();
QString network();
QString serialNumber();
QString lineOut();
QString control();
private:
int m_playerId;
int m_groupId;
QString m_serialNumber;
QString m_name;
QString m_lineOut;
QString m_network;
QString m_playerModel;
QString m_playerVersion;
QString m_control;
};
#endif // HEOSPLAYER_H

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