440 lines
12 KiB
C++
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);
|
|
}
|
|
}
|
|
}
|