// 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 . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "speedwireinterface.h" #include "extern-plugininfo.h" #include 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(&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(&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 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(); } }