nymea-plugins-modbus/sma/speedwire/speedwireinterface.cpp

252 lines
11 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#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();
}
}