added denon heos

master
nymea 2019-05-10 11:29:39 +02:00
parent 3206f285ba
commit ac3a209afe
8 changed files with 1505 additions and 164 deletions

View File

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

View File

@ -1,7 +1,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
* 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. *
* *
@ -21,89 +21,166 @@
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*!
\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 "plugininfo.h"
#include "network/upnp/upnpdiscovery.h"
#include "network/upnp/upnpdiscoveryreply.h"
#include "network/avahi/qtavahiservicebrowser.h"
#include <QDebug>
#include <QStringList>
#include <QJsonDocument>
DevicePluginDenon::DevicePluginDenon()
{
}
DevicePluginDenon::~DevicePluginDenon()
DeviceManager::DeviceError DevicePluginDenon::discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params)
{
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
}
Q_UNUSED(params)
void DevicePluginDenon::init()
{
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(15);
connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginDenon::onPluginTimer);
if (deviceClassId == AVRX1000DeviceClassId) {
QtAvahiServiceBrowser *avahiBrowser = hardwareManager()->avahiBrowser();
connect(avahiBrowser, &QtAvahiServiceBrowser::serviceEntryAdded, this, &DevicePluginDenon::onAvahiEntryAdded);
return DeviceManager::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 DeviceManager::DeviceErrorAsync;
}
return DeviceManager::DeviceErrorDeviceClassNotFound;
}
Device::DeviceSetupStatus DevicePluginDenon::setupDevice(Device *device)
{
qCDebug(dcDenon) << "Setup Denon device" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString();
// Check if we already have a denon device
if (!myDevices().isEmpty()) {
qCWarning(dcDenon) << "Could not add denon device. Only one denon device allowed.";
return Device::DeviceSetupStatusFailure;
if(!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginDenon::onPluginTimer);
}
QHostAddress address(device->paramValue(AVRX1000DeviceIpParamTypeId).toString());
if (address.isNull()) {
qCWarning(dcDenon) << "Could not parse ip address" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString();
return Device::DeviceSetupStatusFailure;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
qCDebug(dcDenon) << "Setup Denon device" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString();
QHostAddress address(device->paramValue(AVRX1000DeviceIpParamTypeId).toString());
if (address.isNull()) {
qCWarning(dcDenon) << "Could not parse ip address" << device->paramValue(AVRX1000DeviceIpParamTypeId).toString();
return DeviceManager::DeviceSetupStatusFailure;
}
DenonConnection *denonConnection = new DenonConnection(address, 23, this);
connect(denonConnection, &DenonConnection::connectionStatusChanged, this, &DevicePluginDenon::onAVRConnectionChanged);
connect(denonConnection, &DenonConnection::socketErrorOccured, this, &DevicePluginDenon::onAVRSocketError);
connect(denonConnection, &DenonConnection::dataReady, this, &DevicePluginDenon::onAVRDataReceived);
m_asyncSetups.append(denonConnection);
denonConnection->connectDenon();
m_denonConnections.insert(device, denonConnection);
return DeviceManager::DeviceSetupStatusAsync;
}
m_device = device;
m_denonConnection = new DenonConnection(address, 23, this);
connect(m_denonConnection.data(), &DenonConnection::connectionStatusChanged, this, &DevicePluginDenon::onConnectionChanged);
connect(m_denonConnection.data(), &DenonConnection::socketErrorOccured, this, &DevicePluginDenon::onSocketError);
connect(m_denonConnection.data(), &DenonConnection::dataReady, this, &DevicePluginDenon::onDataReceived);
if (device->deviceClassId() == heosDeviceClassId) {
qCDebug(dcDenon) << "Setup Denon device" << device->paramValue(heosDeviceIpParamTypeId).toString();
m_asyncSetups.append(m_denonConnection);
m_denonConnection->connectDenon();
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);
return Device::DeviceSetupStatusAsync;
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);
heos->connectHeos();
m_heos.insert(device, heos);
return DeviceManager::DeviceSetupStatusAsync;
}
if (device->deviceClassId() == heosPlayerDeviceClassId) {
return DeviceManager::DeviceSetupStatusSuccess;
}
return DeviceManager::DeviceSetupStatusFailure;
}
void DevicePluginDenon::deviceRemoved(Device *device)
{
qCDebug(dcDenon) << "Delete " << device->name();
if (m_denonConnection.isNull()){
qCWarning(dcDenon) << "Invalid connection pointer" << device->id().toString();
return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
DenonConnection *denonConnection = m_denonConnections.value(device);
m_denonConnections.remove(device);
denonConnection->disconnectDenon();
denonConnection->deleteLater();
}
if (device->deviceClassId() == heosDeviceClassId) {
if (m_denonConnections.contains(device)) {
DenonConnection *denonConnection = m_denonConnections.value(device);
m_denonConnections.remove(device);
denonConnection->disconnectDenon();
denonConnection->deleteLater();
}
}
if (device->deviceClassId() == heosPlayerDeviceClassId) {
}
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)
{
qCDebug(dcDenon) << "Execute action" << device->id() << action.id() << action.params();
if (device->deviceClassId() == AVRX1000DeviceClassId) {
DenonConnection *denonConnection = m_denonConnections.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) {
// Print information that we are executing now the update action
qCDebug(dcDenon) << "set power action" << action.id();
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);
denonConnection->sendData(cmd);
} else {
QByteArray cmd = "PWSTANDBY\r";
qCDebug(dcDenon) << "Execute power: " << action.id() << cmd;
m_denonConnection->sendData(cmd);
denonConnection->sendData(cmd);
}
return Device::DeviceErrorNoError;
@ -114,7 +191,7 @@ Device::DeviceError DevicePluginDenon::executeAction(Device *device, const Actio
QByteArray cmd = "MV" + vol + "\r";
qCDebug(dcDenon) << "Execute volume" << action.id() << cmd;
m_denonConnection->sendData(cmd);
denonConnection->sendData(cmd);
return Device::DeviceErrorNoError;
@ -125,134 +202,454 @@ Device::DeviceError DevicePluginDenon::executeAction(Device *device, const Actio
QByteArray cmd = "SI" + channel + "\r";
qCDebug(dcDenon) << "Change to channel:" << cmd;
m_denonConnection->sendData(cmd);
denonConnection->sendData(cmd);
return Device::DeviceErrorNoError;
return DeviceManager::DeviceErrorNoError;
} else if (action.actionTypeId() == AVRX1000IncreaseVolumeActionTypeId) {
QByteArray cmd = "MVUP\r";
qCDebug(dcDenon) << "Execute volume increase" << action.id() << cmd;
denonConnection->sendData(cmd);
return DeviceManager::DeviceErrorNoError;
} else if (action.actionTypeId() == AVRX1000DecreaseVolumeActionTypeId) {
QByteArray cmd = "MVDOWN\r";
qCDebug(dcDenon) << "Execute volume decrease" << action.id() << cmd;
denonConnection->sendData(cmd);
return DeviceManager::DeviceErrorNoError;
}
return Device::DeviceErrorActionTypeNotFound;
return DeviceManager::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 DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerMuteActionTypeId) {
bool mute = action.param(heosPlayerMuteActionMuteParamTypeId).value().toBool();
heos->setMute(playerId, mute);
return DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPlaybackStatusActionTypeId) {
QString playbackStatus = action.param(heosPlayerPlaybackStatusActionPlaybackStatusParamTypeId).value().toString();
if (playbackStatus == "playing") {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Play);
} else if (playbackStatus == "stopping") {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Stop);
} else if (playbackStatus == "pausing") {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Pause);
}
return DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerShuffleActionTypeId) {
bool shuffle = action.param(heosPlayerShuffleActionShuffleParamTypeId).value().toBool();
Heos::HeosRepeatMode repeatMode;
repeatMode = Heos::HeosRepeatMode::Off;
heos->setPlayMode(playerId, repeatMode, shuffle);
return DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerSkipBackActionTypeId) {
heos->playPrevious(playerId);
return DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerFastRewindActionTypeId) {
return DeviceManager::DeviceErrorActionTypeNotFound;
}
if (action.actionTypeId() == heosPlayerStopActionTypeId) {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Stop);
return DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPlayActionTypeId) {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Play);
return DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerPauseActionTypeId) {
heos->setPlayerState(playerId, Heos::HeosPlayerState::Pause);
return DeviceManager::DeviceErrorNoError;
}
if (action.actionTypeId() == heosPlayerFastForwardActionTypeId) {
return DeviceManager::DeviceErrorActionTypeNotFound;
}
if (action.actionTypeId() == heosPlayerSkipNextActionTypeId) {
heos->playNext(playerId);
return DeviceManager::DeviceErrorNoError;
}
return DeviceManager::DeviceErrorActionTypeNotFound;
}
return Device::DeviceErrorDeviceClassNotFound;
}
void DevicePluginDenon::postSetupDevice(Device *device)
{
if (device->deviceClassId() == AVRX1000DeviceClassId) {
}
if (device->deviceClassId() == heosDeviceClassId) {
Heos *heos = m_heos.value(device);
heos->getPlayers();
device->setStateValue(heosConnectedStateTypeId, heos->connected());
}
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);
device->setStateValue(heosPlayerConnectedStateTypeId, heos->connected());
}
}
void DevicePluginDenon::startMonitoringAutoDevices()
{
}
void DevicePluginDenon::onPluginTimer()
{
if (m_denonConnection.isNull())
return;
if (!m_denonConnection->connected()) {
m_denonConnection->connectDenon();
} else {
m_denonConnection->sendData("PW?\rSI?\rMV?\r");
}
}
void DevicePluginDenon::onConnectionChanged()
{
if (!m_device)
return;
// if the device is connected
if (m_denonConnection->connected()) {
// and from the first setup
if (m_asyncSetups.contains(m_denonConnection)) {
m_asyncSetups.removeAll(m_denonConnection);
m_denonConnection->sendData("PW?\rSI?\rMV?\r");
emit deviceSetupFinished(m_device, Device::DeviceSetupStatusSuccess);
foreach(DenonConnection *denonConnection, m_denonConnections.values()) {
if (!denonConnection->connected()) {
denonConnection->connectDenon();
}
Device *device = m_denonConnections.key(denonConnection);
if (device->deviceClassId() == AVRX1000DeviceClassId) {
denonConnection->sendData("PW?\rSI?\rMV?\r");
}
}
// Set connection status
m_device->setStateValue(AVRX1000ConnectedStateTypeId, m_denonConnection->connected());
foreach(Device *device, myDevices()) {
if (device->deviceClassId() == heosDeviceClassId) {
Heos *heos = m_heos.value(device);
if (!heos->connected()) {
heos->connectHeos();
}
device->setStateValue(heosConnectedStateTypeId, heos->connected());
heos->getPlayers();
heos->registerForChangeEvents(true);
}
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::onDataReceived(const QByteArray &data)
void DevicePluginDenon::onAVRConnectionChanged()
{
DenonConnection *denonConnection = static_cast<DenonConnection *>(sender());
Device *device = m_denonConnections.key(denonConnection);
if (device->deviceClassId() == AVRX1000DeviceClassId) {
// if the device is connected
if (denonConnection->connected()) {
// and from the first setup
if (m_asyncSetups.contains(denonConnection)) {
m_asyncSetups.removeAll(denonConnection);
denonConnection->sendData("PW?\rSI?\rMV?\r");
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess);
}
}
device->setStateValue(AVRX1000ConnectedStateTypeId, denonConnection->connected());
}
}
void DevicePluginDenon::onAVRDataReceived(const QByteArray &data)
{
DenonConnection *denonConnection = static_cast<DenonConnection *>(sender());
Device *device = m_denonConnections.key(denonConnection);
qCDebug(dcDenon) << "Data received" << data;
// if there is no device, return
if (m_device.isNull())
return;
if (device->deviceClassId() == AVRX1000DeviceClassId) {
if (data.contains("MV") && !data.contains("MAX")){
int index = data.indexOf("MV");
int vol = data.mid(index+2, 2).toInt();
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 volume:" << vol;
device->setStateValue(AVRX1000VolumeStateTypeId, vol);
}
qCDebug(dcDenon) << "Update channel:" << cmd;
m_device->setStateValue(AVRX1000ChannelStateTypeId, cmd);
}
if (data.contains("SI")) {
QString cmd = NULL;
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";
}
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);
qCDebug(dcDenon) << "Update channel:" << cmd;
device->setStateValue(AVRX1000ChannelStateTypeId, cmd);
}
if (data.contains("PWON")) {
qCDebug(dcDenon) << "Update power on";
device->setStateValue(AVRX1000PowerStateTypeId, true);
} else if (data.contains("PWSTANDBY")) {
qCDebug(dcDenon) << "Update power off";
device->setStateValue(AVRX1000PowerStateTypeId, false);
}
}
}
void DevicePluginDenon::onSocketError()
void DevicePluginDenon::onAVRSocketError()
{
// if there is no device, return
if (m_device.isNull())
return;
DenonConnection *denonConnection = static_cast<DenonConnection *>(sender());
Device *device = m_denonConnections.key(denonConnection);
if (device->deviceClassId() == AVRX1000DeviceClassId) {
// Check if setup running for this device
if (m_asyncSetups.contains(m_denonConnection)) {
qCWarning(dcDenon()) << "Could not add device. The setup failed.";
emit deviceSetupFinished(m_device, Device::DeviceSetupStatusFailure);
// Delete the connection, the device will not be added and
// the connection will be created in the next setup
m_denonConnection->deleteLater();
// Check if setup running for this device
if (m_asyncSetups.contains(denonConnection)) {
m_asyncSetups.removeAll(denonConnection);
qCWarning(dcDenon()) << "Could not add device. The setup failed.";
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
// Delete the connection, the device will not be added and
// the connection will be created in the next setup
denonConnection->deleteLater();
m_denonConnections.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<QString> serialNumbers;
foreach (Device *device, myDevices()) {
if (device->deviceClassId() == heosDeviceClassId){
serialNumbers.append(device->paramValue(heosDeviceSerialNumberParamTypeId).toString());
}
}
QList<DeviceDescriptor> deviceDescriptors;
foreach (const UpnpDeviceDescriptor &upnpDevice, reply->deviceDescriptors()) {
qCDebug(dcDenon) << "UPnP device found:" << upnpDevice.modelDescription() << upnpDevice.friendlyName() << upnpDevice.hostAddress().toString() << upnpDevice.modelName() << upnpDevice.manufacturer() << upnpDevice.serialNumber() << upnpDevice.deviceType() << upnpDevice.location();
if (upnpDevice.modelName().contains("HEOS")) {
//check if not already addded
QString serialNumber = upnpDevice.serialNumber();
if ((!serialNumbers.contains(serialNumber)) && (serialNumber !=("0000001"))) {
DeviceDescriptor descriptor(heosDeviceClassId, upnpDevice.modelName(), serialNumber);
ParamList params;
params.append(Param(heosDeviceModelNameParamTypeId, upnpDevice.modelName()));
params.append(Param(heosDeviceIpParamTypeId, upnpDevice.hostAddress().toString()));
params.append(Param(heosDeviceSerialNumberParamTypeId, serialNumber));
descriptor.setParams(params);
deviceDescriptors.append(descriptor);
serialNumbers.append(serialNumber);
}
}
}
emit devicesDiscovered(heosDeviceClassId, deviceDescriptors);
}
void DevicePluginDenon::onAvahiEntryAdded() {
qCDebug(dcDenon) << "Avahi entry added";
}
void DevicePluginDenon::onHeosConnectionChanged()
{
Heos *heos = static_cast<Heos *>(sender());
heos->registerForChangeEvents(true);
Device *device = m_heos.key(heos);
if (!device->setupComplete() && heos->connected()) {
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess);
}
}
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, Heos::HeosPlayerState state)
{
foreach (Device *device, myDevices()) {
if(device->deviceClassId() == heosPlayerDeviceClassId) {
if (device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt() == playerId) {
if (state == Heos::HeosPlayerState::Pause) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Paused");
} else if (state == Heos::HeosPlayerState::Play) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Playing");
} else if (state == Heos::HeosPlayerState::Stop) {
device->setStateValue(heosPlayerPlaybackStatusStateTypeId, "Stopped");
}
}
}
}
}
void DevicePluginDenon::onHeosRepeatModeReceived(int playerId, Heos::HeosRepeatMode repeatMode)
{
foreach (Device *device, myDevices()) {
if(device->deviceClassId() == heosPlayerDeviceClassId) {
if (device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt() == playerId) {
if (repeatMode == Heos::HeosRepeatMode::All) {
device->setStateValue(heosPlayerRepeatStateTypeId, "All");
} else if (repeatMode == Heos::HeosRepeatMode::One) {
device->setStateValue(heosPlayerRepeatStateTypeId, "One");
} else if (repeatMode == Heos::HeosRepeatMode::Off) {
device->setStateValue(heosPlayerRepeatStateTypeId, "None");
}
}
}
}
}
void DevicePluginDenon::onHeosShuffleModeReceived(int playerId, bool shuffle)
{
foreach (Device *device, myDevices()) {
if(device->deviceClassId() == heosPlayerDeviceClassId) {
if (device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt() == playerId) {
if (shuffle) {
device->setStateValue(heosPlayerMuteStateTypeId, true);
} else {
device->setStateValue(heosPlayerMuteStateTypeId, false);
}
}
}
}
}
void DevicePluginDenon::onHeosMuteStatusReceived(int playerId, bool mute)
{
foreach (Device *device, myDevices()) {
if(device->deviceClassId() == heosPlayerDeviceClassId) {
if (device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt() == playerId) {
device->setStateValue(heosPlayerMuteStateTypeId, mute);
}
}
}
}
void DevicePluginDenon::onHeosVolumeStatusReceived(int playerId, int volume)
{
foreach (Device *device, myDevices()) {
if(device->deviceClassId() == heosPlayerDeviceClassId) {
if (device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt() == playerId) {
device->setStateValue(heosPlayerVolumeStateTypeId, volume);
}
}
}
}
void DevicePluginDenon::onHeosNowPlayingMediaStatusReceived(int playerId, QString source, QString artist, QString album, QString song, QString artwork)
{
foreach (Device *device, myDevices()) {
if(device->deviceClassId() == heosPlayerDeviceClassId) {
if (device->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt() == playerId) {
device->setStateValue(heosPlayerArtistStateTypeId, artist);
device->setStateValue(heosPlayerTitleStateTypeId, song);
device->setStateValue(heosPlayerArtworkStateTypeId, artwork);
device->setStateValue(heosPlayerCollectionStateTypeId, album);
device->setStateValue(heosPlayerSourceStateTypeId, source);
}
}
}
}

View File

@ -1,7 +1,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
* 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. *
* *
@ -26,7 +26,6 @@
#include "devices/deviceplugin.h"
#include <QHash>
#include <QObject>
#include <QPointer>
@ -35,6 +34,8 @@
#include "plugintimer.h"
#include "denonconnection.h"
#include "heos.h"
#include <QPair>
class DevicePluginDenon : public DevicePlugin
{
@ -45,27 +46,43 @@ class DevicePluginDenon : public DevicePlugin
public:
explicit DevicePluginDenon();
~DevicePluginDenon();
void init() override;
Device::DeviceSetupStatus setupDevice(Device *device) override;
void startMonitoringAutoDevices() override;
DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params) override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
void postSetupDevice(Device * device) override;
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
void deviceRemoved(Device *device) override;
Device::DeviceError executeAction(Device *device, const Action &action) override;
private:
PluginTimer *m_pluginTimer = nullptr;
QPointer<Device> m_device;
QPointer<DenonConnection> m_denonConnection;
QHash<Device *, DenonConnection*> m_denonConnections;
QHash<Device *, Heos*> m_heos;
QList<DenonConnection *> m_asyncSetups;
QHash <ActionId, Device *> m_asyncActions;
QHash <QNetworkReply *, ActionId> m_asyncActionReplies;
QHash<int, Device *> m_playerIds;
QHash<int, Device *> m_discoveredPlayerIds;
QHash<const Action *, int> m_asyncActions;
private slots:
void onPluginTimer();
void onConnectionChanged();
void onDataReceived(const QByteArray &data);
void onSocketError();
void onAVRConnectionChanged();
void onAVRDataReceived(const QByteArray &data);
void onAVRSocketError();
void onUpnpDiscoveryFinished();
void onAvahiEntryAdded();
void onHeosConnectionChanged();
void onHeosPlayerDiscovered(HeosPlayer *heosPlayer);
void onHeosPlayStateReceived(int playerId, Heos::HeosPlayerState state);
void onHeosShuffleModeReceived(int playerId, bool shuffle);
void onHeosRepeatModeReceived(int playerId, Heos::HeosRepeatMode repeatMode);
void onHeosMuteStatusReceived(int playerId, bool mute);
void onHeosVolumeStatusReceived(int playerId, int volume);
void onHeosNowPlayingMediaStatusReceived(int playerId, QString source, QString artist, QString album, QString Song, QString artwork);
};
#endif // DEVICEPLUGINDENON_H

View File

@ -13,6 +13,8 @@
"name": "AVRX1000",
"displayName": "AVR X1000",
"createMethods": ["user"],
"deviceIcon": "Hifi",
"interfaces": ["connectable", "power", "extendedvolumecontroller"],
"paramTypes": [
{
"id": "a54b98b4-b78f-41dd-a257-14425c6cf9ab",
@ -42,16 +44,25 @@
"writable": true
},
{
"displayName": "volume",
"displayName": "Mute",
"id": "3e11470d-a5b7-499c-be55-9b1b4fe5eedf",
"name": "mute",
"displayNameEvent": "Mute changed",
"displayNameAction": "Set mute",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"displayName": "Volume",
"id": "773636b9-304d-463a-8755-fc7488dc0ff3",
"name": "volume",
"displayNameEvent": "volume changed",
"displayNameEvent": "Volume changed",
"displayNameAction": "Set volume",
"type": "int",
"unit": "Dezibel",
"defaultValue": 0,
"minValue": 0,
"maxValue": 80,
"maxValue": 100,
"writable": true
},
{
@ -85,6 +96,251 @@
],
"defaultValue": "TUNER"
}
],
"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": "765c7e2a-9eb6-46fc-a880-4e96c81f8d1e",
"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": ["connectable", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat" ],
"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": "f796664d-6cb7-4f29-9d05-771968d82a32",
"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": ""
}
],
"actionTypes": [
{
"id": "a718f7e9-0b54-4403-b661-49f7b0d13085",
"name": "skipBack",
"displayName": "skip back"
},
{
"id": "fe42d89f-aaad-4f33-a022-d80bdf3a7b19",
"name": "fastRewind",
"displayName": "fast rewind"
},
{
"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": "60b62e88-c68b-463f-b328-2c5d67a71ca0",
"name": "fastForward",
"displayName": "fast forward"
},
{
"id": "57697e9c-ce5e-4b8f-b42e-16662829ceb2",
"name": "skipNext",
"displayName": "skip next"
}
]
}
]

475
denon/heos.cpp Normal file
View File

@ -0,0 +1,475 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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>
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)));
}
void Heos::getPlayers()
{
QByteArray cmd = "heos://player/get_players\r\n";
m_socket->write(cmd);
}
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::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, HeosPlayerState state)
{
QByteArray playerStateQuery;
if (state == HeosPlayerState::Play){
playerStateQuery = "&state=play";
} else if (state == HeosPlayerState::Pause){
playerStateQuery = "&state=pause";
} else if (state == HeosPlayerState::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, HeosRepeatMode repeatMode, bool shuffle)
{
QByteArray repeatModeQuery;
if (repeatMode == HeosRepeatMode::Off) {
repeatModeQuery = "&repeat=off";
} else if (repeatMode == HeosRepeatMode::One) {
repeatModeQuery = "&repeat=on_one";
} else if (repeatMode == HeosRepeatMode::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::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::getNowPlayingMedia(int playerId)
{
QByteArray cmd = "heos://player/get_now_playing_media?pid=" + QVariant(playerId).toByteArray() + "\r\n";
m_socket->write(cmd);
}
Heos::~Heos()
{
m_socket->close();
}
bool Heos::connected()
{
return m_connected;
}
void Heos::connectHeos()
{
if (m_socket->state() == QAbstractSocket::ConnectingState) {
return;
}
m_socket->connectToHost(m_hostAddress, 1255);
}
HeosPlayer *Heos::getPlayer(int playerId)
{
return m_heosPlayers.value(playerId);
}
void Heos::onConnected()
{
qCDebug(dcDenon()) << "connected successfully to" << m_hostAddress.toString();
setConnected(true);
}
void Heos::onDisconnected()
{
qCDebug(dcDenon()) << "disconnected from" << m_hostAddress.toString();
setConnected(false);
}
void Heos::onError(QAbstractSocket::SocketError socketError)
{
qCWarning(dcDenon) << "socket error:" << socketError << m_socket->errorString();
}
void Heos::readData()
{
int playerId;
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();
QString source;
switch (dataMap.value("payload").toMap().value("sid").toInt()) {
case 1:
source = "Pandora";
break;
case 2:
source = "Rhapsody";
break;
case 3:
source = "TuneIn";
break;
case 4:
source = "Spotify";
break;
case 5:
source = "Deezer";
break;
case 6:
source = "Napster";
break;
case 7:
source = "iHeartRadio";
break;
case 8:
source = "Sirius XM";
break;
case 9:
source = "Soundcloud";
break;
case 10:
source = "Tidal";
break;
case 11:
source = "Unknown";
break;
case 12:
source = "Rdio";
break;
case 13:
source = "Amazon Music";
break;
case 14:
source = "Unknown";
break;
case 15:
source = "Moodmix";
break;
case 16:
source = "Juke";
break;
case 17:
source = "Unkown";
break;
case 18:
source = "QQMusic";
break;
case 1024:
source = "USB Media/DLNA Servers";
break;
case 1025:
source = "HEOS Playlists";
break;
case 1026:
source = "HEOS History";
break;
case 1027:
source = "HEOS aux inputs";
break;
case 1028:
source = "HEOS aux inputs";
break;
default:
source = "Unknown";
};
emit nowPlayingMediaStatusReceived(playerId, source, artist, album, song, artwork);
}
if (command.contains("get_play_state") || command.contains("set_play_state")) {
if (message.hasQueryItem("state")) {
HeosPlayerState playState = HeosPlayerState::Stop;
if (message.queryItemValue("state").contains("play")) {
playState = HeosPlayerState::Play;
} else if (message.queryItemValue("state").contains("pause")) {
playState = HeosPlayerState::Pause;
} else if (message.queryItemValue("state").contains("stop")) {
playState = HeosPlayerState::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);
HeosRepeatMode repeatMode = HeosRepeatMode::Off;
if (message.queryItemValue("repeat").contains("on_all")){
repeatMode = HeosRepeatMode::All;
} else if (message.queryItemValue("repeat").contains("on_one")){
repeatMode = HeosRepeatMode::One;
} else if (message.queryItemValue("repeat").contains("off")){
repeatMode = HeosRepeatMode::Off;
}
emit repeatModeReceived(playerId, repeatMode);
}
}
if (command.contains("player_state_changed")) {
if (message.hasQueryItem("state")) {
HeosPlayerState playState = HeosPlayerState::Stop;
if (message.queryItemValue("state").contains("play")) {
playState = HeosPlayerState::Play;
} else if (message.queryItemValue("state").contains("pause")) {
playState = HeosPlayerState::Pause;
} else if (message.queryItemValue("state").contains("stop")) {
playState = HeosPlayerState::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")) {
HeosRepeatMode repeatMode = HeosRepeatMode::Off;
if (message.queryItemValue("repeat").contains("on_all")){
repeatMode = HeosRepeatMode::All;
} else if (message.queryItemValue("repeat").contains("on_one")){
repeatMode = HeosRepeatMode::One;
} else if (message.queryItemValue("repeat").contains("off")){
repeatMode = HeosRepeatMode::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);
}
}
}
}
}
void Heos::setConnected(const bool &connected)
{
m_connected = connected;
emit connectionStatusChanged();
}

76
denon/heos.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef HEOS_H
#define HEOS_H
#include <QObject>
#include <QHostAddress>
#include <QTcpSocket>
#include "heosplayer.h"
class Heos : public QObject
{
Q_OBJECT
public:
enum HeosPlayerState {
Play = 0,
Pause = 1,
Stop = 2
};
enum HeosRepeatMode {
Off = 0,
One = 1,
All = 2
};
explicit Heos(const QHostAddress &hostAddress, QObject *parent = 0);
~Heos();
void setAddress(QHostAddress address);
QHostAddress getAddress();
bool connected();
void connectHeos();
void getPlayers();
HeosPlayer *getPlayer(int playerId);
void getPlayerState(int playerId);
void setPlayerState(int playerId, HeosPlayerState state);
void getVolume(int playerId);
void setVolume(int playerId, int volume);
void getMute(int playerId);
void setMute(int playerId, bool state);
void setPlayMode(int playerId, HeosRepeatMode repeatMode, bool shuffle); //shuffle and repead mode
void getPlayMode(int playerId);
void playNext(int playerId);
void playPrevious(int playerId);
void getNowPlayingMedia(int playerId);
void registerForChangeEvents(bool state);
void sendHeartbeat();
private:
bool m_connected;
bool m_eventRegistered;
QHostAddress m_hostAddress;
QTcpSocket *m_socket;
QHash<int, HeosPlayer*> m_heosPlayers;
void setConnected(const bool &connected);
signals:
void playerDiscovered(HeosPlayer *heosPlayer);
void connectionStatusChanged();
void playStateReceived(int playerId, HeosPlayerState state);
void shuffleModeReceived(int playerId, bool shuffle);
void repeatModeReceived(int playerId, HeosRepeatMode repeatMode);
void muteStatusReceived(int playerId, bool mute);
void volumeStatusReceived(int playerId, int volume);
void nowPlayingMediaStatusReceived(int playerId, QString source, QString artist, QString album, QString Song, QString artwork);
private slots:
void onConnected();
void onDisconnected();
void onError(QAbstractSocket::SocketError socketError);
void readData();
};
#endif // HEOS_H

74
denon/heosplayer.cpp Normal file
View File

@ -0,0 +1,74 @@
#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;
}

42
denon/heosplayer.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef HEOSPLAYER_H
#define HEOSPLAYER_H
#include <QObject>
class HeosPlayer : public QObject
{
Q_OBJECT
public:
explicit HeosPlayer(int playerId, QObject *parent = 0);
explicit HeosPlayer(int playerId, QString name, QString serialNumber, QObject *parent = 0);
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;
signals:
public slots:
};
#endif // HEOSPLAYER_H