improve UPnP discovery

pull/1/head
Michael Zanetti 2018-06-05 17:12:52 +02:00
parent 230e1af700
commit 362029b0c3
4 changed files with 95 additions and 85 deletions

View File

@ -21,13 +21,10 @@ void NymeaDiscovery::setDiscovering(bool discovering)
if (m_discovering != discovering) {
m_discovering = discovering;
// For zeroconf we'll ignore it as zeroconf doesn't do active discovery but just listens for changes in the net all the time
// If we don't have zeroconf available, start an active upnp discovery
if (!m_zeroConf->available()) {
if (discovering) {
m_upnp->discover();
} else {
m_upnp->stopDiscovery();
}
if (discovering) {
m_upnp->discover();
} else {
m_upnp->stopDiscovery();
}
emit discoveringChanged();
}

View File

@ -23,12 +23,11 @@
#include <QDebug>
#include <QUrl>
#include <QXmlStreamReader>
#include <QNetworkInterface>
UpnpDiscovery::UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent) :
QUdpSocket(parent),
m_discoveryModel(discoveryModel),
m_discovering(false),
m_available(false)
QObject(parent),
m_discoveryModel(discoveryModel)
{
m_networkAccessManager = new QNetworkAccessManager(this);
connect(m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpnpDiscovery::networkReplyFinished);
@ -36,76 +35,65 @@ UpnpDiscovery::UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent) :
m_repeatTimer.setInterval(500);
connect(&m_repeatTimer, &QTimer::timeout, this, &UpnpDiscovery::writeDiscoveryPacket);
// bind udp socket and join multicast group
m_port = 1900;
m_host = QHostAddress("239.255.255.250");
setSocketOption(QAbstractSocket::MulticastTtlOption,QVariant(1));
setSocketOption(QAbstractSocket::MulticastLoopbackOption,QVariant(1));
if(!bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress)){
qWarning() << "UPnP discovery could not bind to port" << m_port;
setAvailable(false);
return;
foreach (const QNetworkInterface &iface, QNetworkInterface::allInterfaces()) {
if (!iface.flags().testFlag(QNetworkInterface::CanMulticast)) {
continue;
}
foreach (const QNetworkAddressEntry &netAddressEntry, iface.addressEntries()) {
if (netAddressEntry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
QUdpSocket *socket = new QUdpSocket(this);
int port = -1;
for (int i = 49125; i < 65535; i++) {
if(socket->bind(netAddressEntry.ip(), i, QUdpSocket::DontShareAddress)){
port = i;
break;
}
}
if (port == 65535 || socket->state() != QUdpSocket::BoundState) {
socket->deleteLater();
qWarning() << "UPnP discovery could not bind to interface" << netAddressEntry.ip();
continue;
}
qDebug() << "Discovering on" << netAddressEntry.ip() << port;
m_sockets.append(socket);
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
connect(socket, &QUdpSocket::readyRead, this, &UpnpDiscovery::readData);
}
}
}
if(!joinMulticastGroup(m_host)){
qWarning() << "UPnP discovery could not join multicast group" << m_host;
setAvailable(false);
return;
}
connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
connect(this, &UpnpDiscovery::readyRead, this, &UpnpDiscovery::readData);
setAvailable(true);
}
bool UpnpDiscovery::discovering() const
{
return m_discovering;
return m_repeatTimer.isActive();
}
bool UpnpDiscovery::available() const
{
return m_available;
return !m_sockets.isEmpty();
}
void UpnpDiscovery::discover()
{
if (!m_available) {
if (!available()) {
qWarning() << "Could not discover. UPnP not available.";
return;
}
qDebug() << "start discovering...";
m_repeatTimer.start();
// m_discoveryModel->clearModel();
m_foundDevices.clear();
setDiscovering(true);
writeDiscoveryPacket();
emit discoveringChanged();
}
void UpnpDiscovery::stopDiscovery()
{
qDebug() << "stop discovering";
m_repeatTimer.stop();
setDiscovering(false);
}
void UpnpDiscovery::setDiscovering(const bool &discovering)
{
m_discovering = discovering;
emit discoveringChanged();
}
void UpnpDiscovery::setAvailable(const bool &available)
{
m_available = available;
emit availableChanged();
}
void UpnpDiscovery::writeDiscoveryPacket()
{
QByteArray ssdpSearchMessage = QByteArray("M-SEARCH * HTTP/1.1\r\n"
@ -114,33 +102,43 @@ void UpnpDiscovery::writeDiscoveryPacket()
"MX:2\r\n"
"ST: ssdp:all\r\n\r\n");
// qDebug() << "sending discovery packet";
writeDatagram(ssdpSearchMessage, m_host, m_port);
qDebug() << "sending discovery package";
foreach (QUdpSocket* socket, m_sockets) {
quint64 ret = socket->writeDatagram(ssdpSearchMessage, QHostAddress("239.255.255.250"), 1900);
if (ret != ssdpSearchMessage.length()) {
qWarning() << "Error sending SSDP query on socket" << socket->localAddress();
}
}
}
void UpnpDiscovery::error(QAbstractSocket::SocketError error)
{
qWarning() << "UPnP socket error:" << error << errorString();
QUdpSocket* socket = static_cast<QUdpSocket*>(sender());
qWarning() << "UPnP socket error:" << error << socket->errorString();
}
void UpnpDiscovery::readData()
{
QUdpSocket* socket = static_cast<QUdpSocket*>(sender());
QByteArray data;
quint16 port;
QHostAddress hostAddress;
// read the answere from the multicast
while (hasPendingDatagrams()) {
data.resize(pendingDatagramSize());
readDatagram(data.data(), data.size(), &hostAddress, &port);
while (socket->hasPendingDatagrams()) {
data.resize(socket->pendingDatagramSize());
socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
}
if (!discovering()) {
return;
}
// qDebug() << "upnp packet" << data;
// if the data contains the HTTP OK header...
if (data.contains("HTTP/1.1 200 OK") || data.contains("NOTIFY * HTTP/1.1")) {
if (data.contains("HTTP/1.1 200 OK")) {
QUrl location;
bool isNymea = false;
@ -206,42 +204,63 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
if (xml.isStartDocument())
continue;
if (xml.isStartElement()) {
// Check for old style websocketURL and nymeaRpcURL
if (xml.name().toString() == "websocketURL") {
QUrl u(xml.readElementText());
PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(PortConfig::ProtocolWebSocket);
pc->setSslEnabled(u.scheme().endsWith('s'));
pc->setSslEnabled(u.scheme() == "wss");
portConfigList.append(pc);
}
}
if (xml.isStartElement()) {
if (xml.name().toString() == "nymeaRpcURL") {
QUrl u(xml.readElementText());
qDebug() << "have url" << u << u.scheme();
PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(PortConfig::ProtocolNymeaRpc);
pc->setSslEnabled(u.scheme().endsWith('s'));
pc->setSslEnabled(u.scheme() == "nymeas");
portConfigList.append(pc);
}
}
if (xml.isStartElement()) {
if (xml.name().toString() == "device") {
while (!xml.atEnd()) {
if (xml.name() == "friendlyName" && xml.isStartElement()) {
name = xml.readElementText();
}
if (xml.name() == "modelNumber" && xml.isStartElement()) {
version = xml.readElementText();
}
if (xml.name() == "UDN" && xml.isStartElement()) {
uuid = xml.readElementText().split(':').last();
}
if (xml.name().toString() == "guhRpcURL") {
QUrl u(xml.readElementText());
qDebug() << "have url" << u << u.scheme();
PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(PortConfig::ProtocolNymeaRpc);
pc->setSslEnabled(u.scheme() == "guhs");
portConfigList.append(pc);
}
// But also for new style serviceList
if (xml.name().toString() == "serviceList") {
while (!(xml.isEndElement() && xml.name().toString() == "serviceList") && !xml.atEnd()) {
xml.readNext();
if (xml.name().toString() == "service") {
while (!(xml.isEndElement() && xml.name().toString() == "service") && !xml.atEnd()) {
xml.readNext();
if (xml.name().toString() == "SCPDURL") {
QUrl u(xml.readElementText());
PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(u.scheme().startsWith("nymea") ? PortConfig::ProtocolNymeaRpc : PortConfig::ProtocolWebSocket);
pc->setSslEnabled(u.scheme() == "nymeas" || u.scheme() == "wss");
portConfigList.append(pc);
}
}
}
}
xml.readNext();
}
if (xml.name() == "friendlyName") {
name = xml.readElementText();
}
if (xml.name() == "modelNumber") {
version = xml.readElementText();
}
if (xml.name() == "UDN") {
uuid = xml.readElementText().split(':').last();
}
}
}

View File

@ -30,7 +30,7 @@
#include "discoverydevice.h"
#include "discoverymodel.h"
class UpnpDiscovery : public QUdpSocket
class UpnpDiscovery : public QObject
{
Q_OBJECT
public:
@ -44,23 +44,16 @@ public:
Q_INVOKABLE void stopDiscovery();
private:
QList<QUdpSocket*> m_sockets;
QNetworkAccessManager *m_networkAccessManager;
QTimer m_repeatTimer;
QHostAddress m_host;
qint16 m_port;
DiscoveryModel *m_discoveryModel;
bool m_discovering;
bool m_available;
QHash<QNetworkReply *, QHostAddress> m_runningReplies;
QList<QUrl> m_foundDevices;
void setDiscovering(const bool &discovering);
void setAvailable(const bool &available);
signals:
void discoveringChanged();
void availableChanged();

View File

@ -582,6 +582,7 @@ Page {
}
onClicked: {
Engine.connection.connect(dialog.discoveryDevice.toUrl(index))
dialog.close()
}
}
}