/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This file is part of guh. * * * * Guh 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, version 2 of the License. * * * * Guh 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 guh. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "tvdiscovery.h" TvDiscovery::TvDiscovery(QObject *parent) : QUdpSocket(parent) { m_timeout = new QTimer(this); m_timeout->setSingleShot(true); connect(m_timeout,SIGNAL(timeout()),this,SLOT(discoverTimeout())); m_manager = new QNetworkAccessManager(this); connect(m_manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(replyFinished(QNetworkReply*))); m_port = 1900; m_host = QHostAddress("239.255.255.250"); setSocketOption(QAbstractSocket::MulticastTtlOption,QVariant(1)); setSocketOption(QAbstractSocket::MulticastLoopbackOption,QVariant(1)); bind(QHostAddress::AnyIPv4,m_port,QUdpSocket::ShareAddress); joinMulticastGroup(m_host); connect(this,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(error(QAbstractSocket::SocketError))); connect(this,SIGNAL(readyRead()),this,SLOT(readData())); } bool TvDiscovery::checkXmlData(QByteArray data) { QByteArray xmlOut; QXmlStreamReader reader(data); QXmlStreamWriter writer(&xmlOut); writer.setAutoFormatting(true); while (!reader.atEnd()) { reader.readNext(); if (!reader.isWhitespace()) { writer.writeCurrentToken(reader); } } if(reader.hasError()){ qDebug() << "ERROR reading XML device information: " << reader.errorString(); qDebug() << "--------------------------------------------"; return false; } m_deviceInformationData = xmlOut; return true; } QString TvDiscovery::printXmlData(QByteArray data) { QString xmlOut; QXmlStreamReader reader(data); QXmlStreamWriter writer(&xmlOut); writer.setAutoFormatting(true); while (!reader.atEnd()) { reader.readNext(); if (!reader.isWhitespace()) { writer.writeCurrentToken(reader); } } if(reader.hasError()){ qDebug() << "ERROR reading XML device information: " << reader.errorString(); qDebug() << "--------------------------------------------"; } return xmlOut; } void TvDiscovery::error(QAbstractSocket::SocketError error) { qWarning() << errorString() << error; } void TvDiscovery::readData() { QByteArray data; QHostAddress sender; quint16 udpPort; // read the answere from the multicast while (hasPendingDatagrams()) { data.resize(pendingDatagramSize()); readDatagram(data.data(), data.size(), &sender, &udpPort); } if(data.size() > 0){ if(data.contains("HTTP/1.1 200 OK")){ const QStringList lines = QString(data).split("\r\n"); QUrl location; QString uuid; QString server; foreach( const QString& line, lines){ int separatorIndex = line.indexOf(':'); QString key = line.left(separatorIndex).toUpper(); QString value = line.mid(separatorIndex+1).trimmed(); // get location if(key.contains("LOCATION")){ location = QUrl(value); } // get server info if(key.contains("SERVER")){ // check if it is a LG Smart Tv with UDAP/2.0 protocoll if(value.contains("UDAP/2.0")){ server = value; } } // get uuid if(key.contains("USN")){ int startIndex = value.indexOf(":"); int endIndex = value.indexOf("::"); uuid = value.mid(startIndex +1 ,(endIndex - startIndex)-1); } if(!location.isEmpty() && !uuid.isEmpty() && !server.isEmpty()){ foreach (TvDevice *device, m_tvList) { if(device->uuid() == uuid){ return; } } TvDevice *device = new TvDevice(this); device->setLocation(location); device->setHostAddress(sender); device->setUuid(uuid); m_tvList.append(device); requestDeviceInformation(device); } } } } } void TvDiscovery::discoverTimeout() { emit discoveryDone(m_tvList); } void TvDiscovery::requestDeviceInformation(TvDevice *device) { QNetworkRequest deviceRequest; deviceRequest.setUrl(device->location()); deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml")); deviceRequest.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0")); m_deviceInformationReplay = m_manager->get(deviceRequest); } void TvDiscovery::replyFinished(QNetworkReply *reply) { int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray data; switch (status) { case(200): data = reply->readAll(); if(checkXmlData(data)){ parseDeviceInformation(data); } break; case(400): qDebug() << "ERROR: 400 Bad request. The event format is not valid or it has an incorrect value."; qDebug() << "--------------------------------------------"; return; case(401): qDebug() << "ERROR: 401 Unauthorized. An event is sent when a Host and a Controller are not paired."; qDebug() << "--------------------------------------------"; return; case(404): qDebug() << "ERROR: 404 Not Found. The POST path of an event is incorrect."; qDebug() << "--------------------------------------------"; return; case(500): qDebug() << "ERROR: 500 Internal Server Error. Event Execution Failure."; qDebug() << "--------------------------------------------"; return; default: return; } } void TvDiscovery::parseDeviceInformation(QByteArray data) { QXmlStreamReader xml(data); QString name; QString uuid; QString modelName; QString deviceType; QString manufacturer; int port; qDebug() << printXmlData(data); while(!xml.atEnd() && !xml.hasError()){ xml.readNext(); if(xml.isStartDocument()){ continue; } if(xml.isStartElement()){ if(xml.name() == "envelope"){ continue; } //check if we have device part of message if(xml.name() == "device"){ // seems to be device information while(!xml.atEnd()){ if(xml.name() == "deviceType" && xml.isStartElement()){ deviceType = xml.readElementText(); } if(xml.name() == "modelName" && xml.isStartElement()){ modelName = xml.readElementText(); } if(xml.name() == "friendlyName" && xml.isStartElement()){ name = xml.readElementText(); } if(xml.name() == "manufacturer" && xml.isStartElement()){ manufacturer = xml.readElementText(); } if(xml.name() == "uuid" && xml.isStartElement()){ uuid = xml.readElementText(); } //check if we have port part of message if(xml.name() == "port"){ port = xml.readElementText().toInt(); } xml.readNext(); } } } } foreach (TvDevice *device, m_tvList) { // find our device with this uuid if(device->uuid() == uuid){ device->setName(name); device->setModelName(modelName); device->setDeviceType(deviceType); device->setManufacturer(manufacturer); device->setPort(port); qDebug() << "--> fetched TV information..."; qDebug() << "name: " << device->name(); qDebug() << "model name: " << device->modelName(); qDebug() << "device type: " << device->deviceType(); qDebug() << "manufacturer: " << device->manufacturer(); qDebug() << "address: " << device->hostAddress().toString(); qDebug() << "port: " << device->port(); qDebug() << "location: " << device->location().toString(); qDebug() << "uuid: " << device->uuid(); qDebug() << "--------------------------------------------"; } } } void TvDiscovery::discover(int timeout) { QString searchMessage("M-SEARCH * HTTP/1.1\r\n" "HOST:239.255.255.250:1900\r\n" "MAN:\"ssdp:discover\"\r\n" "MX:2\r\n" "ST:udap:rootservice\r\n" "USER-AGENT: UDAP/2.0 \r\n\r\n"); m_tvList.clear(); writeDatagram(searchMessage.toUtf8(),m_host,m_port); m_timeout->start(timeout); }