246 lines
10 KiB
C++
246 lines
10 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-modbus.
|
|
*
|
|
* nymea-plugins-modbus 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-modbus 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-modbus. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "speedwireinterface.h"
|
|
#include "extern-plugininfo.h"
|
|
|
|
#include <QNetworkInterface>
|
|
|
|
SpeedwireInterface::SpeedwireInterface(quint32 sourceSerialNumber, QObject *parent) :
|
|
QObject(parent),
|
|
m_sourceSerialNumber(sourceSerialNumber)
|
|
{
|
|
m_unicast = new QUdpSocket(this);
|
|
connect(m_unicast, &QUdpSocket::readyRead, this, [=](){
|
|
QByteArray datagram;
|
|
QHostAddress senderAddress;
|
|
quint16 senderPort;
|
|
|
|
while (m_unicast->hasPendingDatagrams()) {
|
|
datagram.resize(m_unicast->pendingDatagramSize());
|
|
m_unicast->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
|
|
|
|
qCDebug(dcSma()).noquote() << "SpeedwireInterface: Unicast socket received data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
|
|
qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex();
|
|
emit dataReceived(senderAddress, senderPort, datagram, false);
|
|
}
|
|
});
|
|
|
|
connect(m_unicast, &QUdpSocket::stateChanged, this, [=](QAbstractSocket::SocketState socketState){
|
|
qCDebug(dcSma()) << "SpeedwireInterface: Unicast socket state changed" << socketState;
|
|
});
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
connect(m_unicast, &QUdpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError error){
|
|
#else
|
|
connect(m_unicast, static_cast<void (QUdpSocket::*)( QAbstractSocket::SocketError )>(&QAbstractSocket::error), this, [=](QAbstractSocket::SocketError error){
|
|
#endif
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Unicast socket error occurred" << error << m_unicast->errorString();
|
|
});
|
|
|
|
|
|
m_multicast = new QUdpSocket(this);
|
|
connect(m_multicast, &QUdpSocket::readyRead, this, [=](){
|
|
QByteArray datagram;
|
|
QHostAddress senderAddress;
|
|
quint16 senderPort;
|
|
|
|
while (m_multicast->hasPendingDatagrams()) {
|
|
datagram.resize(m_multicast->pendingDatagramSize());
|
|
m_multicast->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
|
|
|
|
// Filter our own requests on the multicast
|
|
if (isOwnInterface(senderAddress))
|
|
return;
|
|
|
|
qCDebug(dcSma()).noquote() << "SpeedwireInterface: Multicast socket received data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
|
|
//qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex();
|
|
emit dataReceived(senderAddress, senderPort, datagram, true);
|
|
}
|
|
});
|
|
|
|
connect(m_multicast, &QUdpSocket::stateChanged, this, [=](QAbstractSocket::SocketState socketState){
|
|
qCDebug(dcSma()) << "SpeedwireInterface: Multicast socket state changed" << socketState;
|
|
});
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
connect(m_multicast, &QUdpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError error){
|
|
#else
|
|
connect(m_multicast, static_cast<void (QUdpSocket::*)( QAbstractSocket::SocketError )>(&QAbstractSocket::error), this, [=](QAbstractSocket::SocketError error){
|
|
#endif
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Multicast socket error occurred" << error << m_multicast->errorString();
|
|
});
|
|
|
|
if (initialize()) {
|
|
qCDebug(dcSma()) << "SpeedwireInterface: Initialized sucessfully unicast and multicast interface.";
|
|
} else {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to initialize.";
|
|
}
|
|
}
|
|
|
|
SpeedwireInterface::~SpeedwireInterface()
|
|
{
|
|
if (m_unicast)
|
|
m_unicast->close();
|
|
|
|
if (m_multicast) {
|
|
if (!m_multicast->leaveMulticastGroup(Speedwire::multicastAddress())) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << Speedwire::multicastAddress().toString();
|
|
}
|
|
|
|
m_multicast->close();
|
|
}
|
|
}
|
|
|
|
bool SpeedwireInterface::available() const
|
|
{
|
|
return m_available;
|
|
}
|
|
|
|
bool SpeedwireInterface::isOwnInterface(const QHostAddress &hostAddress)
|
|
{
|
|
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
|
|
if (networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack))
|
|
continue;
|
|
|
|
if (!networkInterface.flags().testFlag(QNetworkInterface::IsUp))
|
|
continue;
|
|
|
|
if (!networkInterface.flags().testFlag(QNetworkInterface::IsRunning))
|
|
continue;
|
|
|
|
foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
|
|
|
|
// Only IPv4
|
|
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
|
|
continue;
|
|
|
|
if (entry.ip() == hostAddress) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SpeedwireInterface::reconfigureMulticastGroup()
|
|
{
|
|
qCDebug(dcSma()) << "Reconfigure multicast interfaces";
|
|
if (m_multicast->joinMulticastGroup(Speedwire::multicastAddress())) {
|
|
qCDebug(dcSma()) << "SpeedwireInterface: Joined successfully multicast group" << Speedwire::multicastAddress().toString();
|
|
m_multicastWarningPrintCount = 0;
|
|
} else {
|
|
// FIXME: It would probably be better to monitor the network interfaces and re-join if necessary
|
|
uint mod = m_multicastWarningPrintCount % 120;
|
|
|
|
if (m_multicastWarningPrintCount < 12) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to join multicast group" << Speedwire::multicastAddress().toString() << m_multicast->errorString() << "Retrying in 5 seconds...";
|
|
}
|
|
|
|
if (m_multicastWarningPrintCount >= 12 && mod == 0) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to join multicast group" << Speedwire::multicastAddress().toString() << m_multicast->errorString() << "Retrying in 10 minutes...";
|
|
}
|
|
|
|
QTimer::singleShot(5000, this, &SpeedwireInterface::reconfigureMulticastGroup);
|
|
m_multicastWarningPrintCount++;
|
|
}
|
|
|
|
// foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) {
|
|
// if(interface.isValid() && !interface.flags().testFlag(QNetworkInterface::IsLoopBack)
|
|
// && interface.flags().testFlag(QNetworkInterface::CanMulticast)
|
|
// && interface.flags().testFlag(QNetworkInterface::IsRunning)) {
|
|
|
|
// QList<QNetworkAddressEntry> addressEntries = interface.addressEntries();
|
|
// for (int i = 0; i < addressEntries.length(); i++) {
|
|
// if (addressEntries.at(i).ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
|
|
|
// if (!m_multicast->joinMulticastGroup(Speedwire::multicastAddress(), interface)) {
|
|
// qCWarning(dcSma()) << "SpeedwireInterface: Could not join multicast group" << Speedwire::multicastAddress().toString() << "on interface" << interface << m_multicast->errorString();
|
|
// } else {
|
|
// qCDebug(dcSma()) << "SpeedwireInterface: Joined successfully multicast group" << Speedwire::multicastAddress().toString() << "on interface" << interface ;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// qCDebug(dcSma()) << "Multicast outgoing interface" << m_multicast->multicastInterface();
|
|
}
|
|
|
|
quint32 SpeedwireInterface::sourceSerialNumber() const
|
|
{
|
|
return m_sourceSerialNumber;
|
|
}
|
|
|
|
bool SpeedwireInterface::initialize()
|
|
{
|
|
bool success = true;
|
|
if (m_unicast->state() != QUdpSocket::BoundState) {
|
|
m_unicast->close();
|
|
if (!m_unicast->bind(QHostAddress::AnyIPv4, Speedwire::port(), QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Unicast socket could not be bound to port" << Speedwire::port();
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
if (m_multicast->state() != QUdpSocket::BoundState) {
|
|
if (!m_multicast->bind(QHostAddress::AnyIPv4, Speedwire::port(), QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Unicast socket could not be bound to port" << Speedwire::port();
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
|
|
reconfigureMulticastGroup();
|
|
m_available = success;
|
|
return success;
|
|
}
|
|
|
|
void SpeedwireInterface::sendDataUnicast(const QHostAddress &address, const QByteArray &data)
|
|
{
|
|
qCDebug(dcSma()) << "SpeedwireInterface: Unicast -->" << address.toString() << Speedwire::port() << data.toHex();
|
|
|
|
if (!m_unicast) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to send unicast data, the socket is not available";
|
|
return;
|
|
}
|
|
|
|
if (m_unicast->writeDatagram(data, address, Speedwire::port()) < 0) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to send unicast data to" << address.toString() << m_unicast->errorString();
|
|
}
|
|
}
|
|
|
|
void SpeedwireInterface::sendDataMulticast(const QByteArray &data)
|
|
{
|
|
qCDebug(dcSma()) << "SpeedwireInterface: Multicast -->" << Speedwire::multicastAddress().toString() << Speedwire::port() << data.toHex();
|
|
if (!m_multicast) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to send multicast data, the socket is not available";
|
|
return;
|
|
}
|
|
|
|
if (m_multicast->writeDatagram(data, Speedwire::multicastAddress(), Speedwire::port()) < 0) {
|
|
qCWarning(dcSma()) << "SpeedwireInterface: Failed to send multicast data to" << Speedwire::multicastAddress().toString() << m_multicast->errorString();
|
|
}
|
|
}
|