nymea-plugins/denon/avrconnection.cpp

440 lines
12 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://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);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(m_socket, &QTcpSocket::errorOccurred, this, &AvrConnection::onError);
#else
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
#endif
m_commandTimer = new QTimer(this);
m_commandTimer->start(50); // 50ms is the minimum request interval specified
connect(m_commandTimer, &QTimer::timeout, this, [this] {
if (!m_commandBuffer.isEmpty()) {
QPair<QUuid, QByteArray> command = m_commandBuffer.takeFirst();
if (m_socket->write(command.second) == -1) {
emit commandExecuted(command.first, false);
qCWarning(dcDenon()) << "Could not execute command" << command.second;
} else {
emit commandExecuted(command.first, true);
}
} else {
m_commandTimer->stop();
}
});
}
AvrConnection::~AvrConnection()
{
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;
}
void AvrConnection::setHostAddress(const QHostAddress &hostAddress)
{
if (m_hostAddress != hostAddress) {
disconnectDevice();
m_hostAddress = hostAddress;
connectDevice();
}
}
int AvrConnection::port() const
{
return m_port;
}
void AvrConnection::setPort(int port)
{
if (m_port != port) {
disconnectDevice();
m_port = port;
connectDevice();
}
}
bool AvrConnection::connected()
{
return m_socket->isOpen();
}
QUuid AvrConnection::getChannel()
{
return sendCommand("SI?\r");
}
QUuid AvrConnection::getVolume()
{
return sendCommand("MV?\r");
}
QUuid AvrConnection::getMute()
{
return sendCommand("MU?\r");
}
QUuid AvrConnection::getPower()
{
return sendCommand("PW?\r");
}
QUuid AvrConnection::getSurroundMode()
{
return sendCommand("MS?\r");
}
QUuid AvrConnection::getPlayBackInfo()
{
return sendCommand("NSE\r");
}
QUuid AvrConnection::sendCommand(const QByteArray &message)
{
QUuid commandId = QUuid::createUuid();
if (!m_commandTimer->isActive())
m_commandTimer->start(50);
m_commandBuffer.append(QPair<QUuid, QByteArray>(commandId, message));
return commandId;
}
QUuid AvrConnection::setChannel(const QByteArray &channel)
{
QByteArray cmd = "SI" + channel + "\r";
qCDebug(dcDenon()) << "Change to channel:" << channel;
return sendCommand(cmd);
}
QUuid AvrConnection::setVolume(uint volume)
{
qCDebug(dcDenon()) << "Set volume" << volume;
QByteArray cmd = "MV" + QByteArray::number(volume) + "\r";
return sendCommand(cmd);
}
QUuid AvrConnection::setMute(bool mute)
{
qCDebug(dcDenon()) << "Set mute" << mute;
QByteArray cmd;
if (mute) {
cmd = "MUON\r";
} else {
cmd = "MUOFF\r";
}
return sendCommand(cmd);
}
QUuid AvrConnection::setPower(bool power)
{
qCDebug(dcDenon()) << "Set power" << power;
QByteArray cmd;
if (power) {
cmd = "PWON\r";
} else {
cmd = "PWSTANDBY\r";
}
return sendCommand(cmd);
}
QUuid AvrConnection::setSurroundMode(const QByteArray &surroundMode)
{
qCDebug(dcDenon()) << "Set surround mode" << surroundMode;
QByteArray cmd = "MS" + surroundMode + "\r";
return sendCommand(cmd);
}
QUuid AvrConnection::enableToneControl(bool enabled)
{
QByteArray cmd;
if (enabled) {
cmd = "PSTONE CTRL ON\r";
} else {
cmd = "PSTONE CTRL OFF\r";
}
return sendCommand(cmd);
}
QUuid AvrConnection::setBassLevel(int level)
{
QByteArray cmd;
cmd = "PSBAS ";
cmd.append(50 + level);
cmd.append("\r");
return sendCommand(cmd);
}
QUuid AvrConnection::setTrebleLevel(int level)
{
QByteArray cmd;
cmd = "PSTRE ";
cmd.append(50 + level);
cmd.append("\r");
return sendCommand(cmd);
}
QUuid AvrConnection::getBassLevel()
{
return sendCommand("PSBAS ?\r");
}
QUuid AvrConnection::getTrebleLevel()
{
return sendCommand("PSTRE ?\r");
}
QUuid AvrConnection::getToneControl()
{
return sendCommand("PSTONE CTRL ?\r");
}
QUuid AvrConnection::play()
{
return sendCommand("NS9A\r");
}
QUuid AvrConnection::pause()
{
return sendCommand("NS9B\r");
}
QUuid AvrConnection::stop()
{
return sendCommand("NS9C\r");
}
QUuid AvrConnection::skipNext()
{
return sendCommand("NS9D\r");
}
QUuid AvrConnection::skipBack()
{
return sendCommand("NS9E\r");
}
QUuid AvrConnection::setRandom(bool on)
{
QByteArray cmd;
if (on) {
cmd = "NS9K\r";
} else {
cmd = "NS9M\r";
}
return sendCommand(cmd);
}
QUuid AvrConnection::setRepeat(AvrConnection::RepeatMode mode)
{
QByteArray cmd;
switch (mode) {
case RepeatModeRepeatAll:
cmd = "NS9I\r";
break;
case RepeatModeRepeatOne:
cmd = "NS9H\r";
break;
case RepeatModeRepeatNone:
cmd = "NS9J\r";
break;
}
return sendCommand(cmd);
}
QUuid AvrConnection::increaseVolume()
{
qCDebug(dcDenon()) << "Execute volume increase";
QByteArray cmd = "MVUP\r";
return sendCommand(cmd);
}
QUuid AvrConnection::decreaseVolume()
{
qCDebug(dcDenon()) << "Execute volume decrease";
QByteArray cmd = "MVDOWN\r";
return 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()
{
QString data = QString(m_socket->readAll());
QStringList lines = data.split('\r');
foreach (QString line, lines) {
if(line.isEmpty())
continue;
qCDebug(dcDenon()) << "Data received" << line;
if (line.contains("MV") && !data.contains("MAX")){
int index = data.indexOf("MV");
int volume = data.mid(index+2, 2).toInt();
emit volumeChanged(volume);
} else if (line.left(2).contains("SI")) {
QByteArray cmd;
if (data.contains("TUNER")) {
cmd = "TUNER";
} else if (data.contains("DVD")) {
cmd = "DVD";
} else if (data.contains("BD")) {
cmd = "BD";
} else if (data.contains("TV")) {
cmd = "TV";
} else if (data.contains("SAT/CBL")) {
cmd = "SAT/CBL";
} else if (data.contains("MPLAY")) {
cmd = "MPLAY";
} else if (data.contains("GAME")) {
cmd = "GAME";
} else if (data.contains("AUX1")) {
cmd = "AUX1";
} else if (data.contains("NET")) {
cmd = "NET";
} else if (data.contains("PANDORA")) {
cmd = "PANDORA";
} else if (data.contains("SIRIUSXM")) {
cmd = "SIRIUSXM";
} else if (data.contains("SPOTIFY")) {
cmd = "SPOTIFY";
} else if (data.contains("FLICKR")) {
cmd = "FLICKR";
} else if (data.contains("FAVORITES")) {
cmd = "FAVORITES";
} else if (data.contains("IRADIO")) {
cmd = "IRADIO";
} else if (data.contains("SERVER")) {
cmd = "SERVER";
} else if (data.contains("USB/IPOD")) {
cmd = "USB/IPOD";
} else if (data.contains("IPD")) {
cmd = "IPD";
} else if (data.contains("IRP")) {
cmd = "IRP";
} else if (data.contains("FVP")) {
cmd = "FVP";
}
emit channelChanged(cmd);
} else if (data.contains("PWON")) {
emit powerChanged(true);
} else if (data.contains("PWSTANDBY")) {
emit powerChanged(false);
} else if (data.contains("MUON")) {
emit muteChanged(true);
} else if (data.contains("MUOFF")) {
emit muteChanged(false);
} else if (data.left(2).contains("MS")) {
QString surroundMode = data.remove(0, 2).trimmed();
qCDebug(dcDenon()) << "Surround mode changed" << surroundMode;
emit surroundModeChanged(surroundMode);
} else if (data.left(4).contains("NSE0")) {
QString nowPlaying = QString(data).remove(0, 4).trimmed();
qCDebug(dcDenon()) << "Playbackstatus" << nowPlaying;
if (nowPlaying.contains("Now Playing")) {
emit playBackModeChanged(PlayBackMode::PlayBackModePlaying);
} else {
emit playBackModeChanged(PlayBackMode::PlayBackModeStopped);
}
} else if (data.left(4).contains("NSE1")) {
QString song = QString(data).remove(0, 5).trimmed();
qCDebug(dcDenon()) << "Song" << song;
emit songChanged(song);
} else if (data.left(4).contains("NSE2")) {
QString artist = QString(data).remove(0, 5).trimmed();
qCDebug(dcDenon()) << "Artist" << artist;
emit artistChanged(artist);
} else if (data.left(4).contains("NSE4")) {
QString album = QString(data).remove(0, 5).trimmed();
qCDebug(dcDenon()) << "Album" << album;
emit albumChanged(album);
} else if (data.contains("PSTONE CTRL ON")) {
qCDebug(dcDenon()) << "Tone control is on";
emit toneControlEnabledChanged(true);
} else if (data.contains("PSTONE CTRL OFF")) {
qCDebug(dcDenon()) << "Tone control is off";
emit toneControlEnabledChanged(false);
} else if (data.contains("PSBAS")) {
int index = data.indexOf("PSBAS");
int bass = data.mid(index+6, 2).toInt() - 50;
qCDebug(dcDenon()) << "Bass level" << bass;
emit bassLevelChanged(bass);
} else if (data.contains("PSTRE")) {
int index = data.indexOf("PSTRE");
int treble = data.mid(index+6, 2).toInt() - 50;
qCDebug(dcDenon()) << "Treble level" << treble;
emit trebleLevelChanged(treble);
}
}
}