// 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 . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #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 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(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); } } }