From 40a435ee9cc4316f2226148212507ccc5f5dfccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sun, 25 Jul 2021 17:02:52 +0200 Subject: [PATCH] Fix UDAP Lg smart tv and start implementing browsable --- lgsmarttv/integrationpluginlgsmarttv.cpp | 473 ++++++++++++++++------ lgsmarttv/integrationpluginlgsmarttv.h | 5 + lgsmarttv/integrationpluginlgsmarttv.json | 162 +++++--- lgsmarttv/tvdevice.cpp | 225 +++++++--- lgsmarttv/tvdevice.h | 73 +++- lgsmarttv/tveventhandler.cpp | 41 +- lgsmarttv/tveventhandler.h | 6 +- 7 files changed, 699 insertions(+), 286 deletions(-) diff --git a/lgsmarttv/integrationpluginlgsmarttv.cpp b/lgsmarttv/integrationpluginlgsmarttv.cpp index 9813f6d1..387c2a4d 100644 --- a/lgsmarttv/integrationpluginlgsmarttv.cpp +++ b/lgsmarttv/integrationpluginlgsmarttv.cpp @@ -55,16 +55,10 @@ void IntegrationPluginLgSmartTv::init() void IntegrationPluginLgSmartTv::discoverThings(ThingDiscoveryInfo *info) { if (info->thingClassId() == lgSmartTvThingClassId) { - - qCDebug(dcLgSmartTv()) << "Start discovering"; + qCDebug(dcLgSmartTv()) << "Start discovering LG UDAP 2.0 smart TVs..."; UpnpDiscoveryReply *reply = hardwareManager()->upnpDiscovery()->discoverDevices("udap:rootservice","UDAP/2.0"); - - // Clean up in any case when the reply finishes connect(reply, &UpnpDiscoveryReply::finished, reply, &UpnpDiscoveryReply::deleteLater); - - // Connect reply to discovery info for rsults. connect(reply, &UpnpDiscoveryReply::finished, info, [this, info, reply](){ - if (reply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) { qCWarning(dcLgSmartTv()) << "Upnp discovery error" << reply->error(); return info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error discovering devices. Please check your network connection.")); @@ -75,13 +69,14 @@ void IntegrationPluginLgSmartTv::discoverThings(ThingDiscoveryInfo *info) continue; qCDebug(dcLgSmartTv) << upnpDeviceDescriptor; - ThingDescriptor descriptor(lgSmartTvThingClassId, "Lg Smart Tv", upnpDeviceDescriptor.modelName()); + ThingDescriptor descriptor(lgSmartTvThingClassId, "LG Smart Tv", upnpDeviceDescriptor.modelName()); ParamList params; params << Param(lgSmartTvThingNameParamTypeId, upnpDeviceDescriptor.friendlyName()); params << Param(lgSmartTvThingUuidParamTypeId, upnpDeviceDescriptor.uuid()); params << Param(lgSmartTvThingModelParamTypeId, upnpDeviceDescriptor.modelName()); params << Param(lgSmartTvThingHostAddressParamTypeId, upnpDeviceDescriptor.hostAddress().toString()); params << Param(lgSmartTvThingPortParamTypeId, upnpDeviceDescriptor.port()); + params << Param(lgSmartTvThingEventHandlerPortParamTypeId, TvEventHandler::getFreePort()); descriptor.setParams(params); foreach (Thing *existingThing, myThings()) { @@ -96,7 +91,7 @@ void IntegrationPluginLgSmartTv::discoverThings(ThingDiscoveryInfo *info) }); } else if (info->thingClassId() == webosTvThingClassId) { - qCDebug(dcLgSmartTv()) << "Start discovering Webos tv"; + qCDebug(dcLgSmartTv()) << "Start discovering WebOs smart TVs..."; UpnpDiscoveryReply *reply = hardwareManager()->upnpDiscovery()->discoverDevices(""); connect(reply, &UpnpDiscoveryReply::finished, reply, &UpnpDiscoveryReply::deleteLater); connect(reply, &UpnpDiscoveryReply::finished, info, [this, info, reply](){ @@ -116,68 +111,69 @@ void IntegrationPluginLgSmartTv::discoverThings(ThingDiscoveryInfo *info) info->finish(Thing::ThingErrorNoError); }); } - } void IntegrationPluginLgSmartTv::startPairing(ThingPairingInfo *info) { - QHostAddress host = QHostAddress(info->params().paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); - int port = info->params().paramValue(lgSmartTvThingPortParamTypeId).toInt(); - QPair request = TvDevice::createDisplayKeyRequest(host, port); + if (info->thingClassId() == lgSmartTvThingClassId) { + QHostAddress host = QHostAddress(info->params().paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); + quint16 port = info->params().paramValue(lgSmartTvThingPortParamTypeId).toUInt(); + QPair request = TvDevice::createDisplayKeyRequest(host, port); - QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); - connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, info, [info, reply](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200) { + qCWarning(dcLgSmartTv) << "display pin on TV request error:" << status << reply->errorString(); + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error connecting to the TV.")); + return; + } - connect(reply, &QNetworkReply::finished, info, [info, reply](){ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status != 200) { - qCWarning(dcLgSmartTv) << "display pin on TV request error:" << status << reply->errorString(); - info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error connecting to the TV.")); - return; - } - - info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the key displayed on the TV.")); - }); + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the key displayed on the TV.")); + }); + } } void IntegrationPluginLgSmartTv::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) { Q_UNUSED(username) - QHostAddress host = QHostAddress(info->params().paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); - int port = info->params().paramValue(lgSmartTvThingPortParamTypeId).toInt(); - QPair request = TvDevice::createPairingRequest(host, port, secret); - QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); - connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - - connect(reply, &QNetworkReply::finished, info, [this, info, reply, secret](){ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if(status != 200) { - qCWarning(dcLgSmartTv) << "pair TV request error:" << status << reply->errorString(); - return info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Error pairing TV. Please try again.")); - } - - // End pairing before calling setupDevice, which will always try to pair - QPair request = TvDevice::createEndPairingRequest(reply->request().url()); + if (info->thingClassId() == lgSmartTvThingClassId) { + QHostAddress host = QHostAddress(info->params().paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); + quint16 port = info->params().paramValue(lgSmartTvThingPortParamTypeId).toUInt(); + quint16 eventHandlerPort = info->params().paramValue(lgSmartTvThingEventHandlerPortParamTypeId).toUInt(); + QPair request = TvDevice::createPairingRequest(host, port, eventHandlerPort, secret); QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - - connect(reply, &QNetworkReply::finished, info, [this, info, reply, secret](){ + connect(reply, &QNetworkReply::finished, info, [=](){ int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if(status != 200) { - qCWarning(dcLgSmartTv) << "end pairing TV request error:" << status << reply->errorString(); - info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Error pairing TV. Please try again.")); - return; + qCWarning(dcLgSmartTv) << "Pair TV request error:" << status << reply->errorString(); + return info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Error pairing TV. Please try again.")); } - pluginStorage()->beginGroup(info->thingId().toString()); - pluginStorage()->setValue("key", secret); - pluginStorage()->endGroup(); + // End pairing before calling setupDevice, which will always try to pair + QPair request = TvDevice::createEndPairingRequest(host, port, eventHandlerPort); + QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, info, [this, info, reply, secret](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if(status != 200) { + qCWarning(dcLgSmartTv) << "End pairing TV request error:" << status << reply->errorString(); + info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Error pairing TV. Please try again.")); + return; + } - info->finish(Thing::ThingErrorNoError); + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("key", secret); + pluginStorage()->endGroup(); + + info->finish(Thing::ThingErrorNoError); + }); }); - }); + } } void IntegrationPluginLgSmartTv::setupThing(ThingSetupInfo *info) @@ -187,7 +183,9 @@ void IntegrationPluginLgSmartTv::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == lgSmartTvThingClassId) { qCDebug(dcLgSmartTv()) << "Setup LG smart TV" << thing->name() << thing->params(); QHostAddress address = QHostAddress(thing->paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); - TvDevice *tvDevice = new TvDevice(address, thing->paramValue(lgSmartTvThingPortParamTypeId).toInt(), this); + quint16 port = thing->paramValue(lgSmartTvThingPortParamTypeId).toUInt(); + quint16 eventHandlerPort = thing->paramValue(lgSmartTvThingEventHandlerPortParamTypeId).toUInt(); + TvDevice *tvDevice = new TvDevice(address, port, eventHandlerPort, this); tvDevice->setUuid(thing->paramValue(lgSmartTvThingUuidParamTypeId).toString()); pluginStorage()->beginGroup(thing->id().toString()); @@ -199,7 +197,6 @@ void IntegrationPluginLgSmartTv::setupThing(ThingSetupInfo *info) connect(tvDevice, &TvDevice::stateChanged, this, &IntegrationPluginLgSmartTv::stateChanged); m_tvList.insert(tvDevice, thing); - if (!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginLgSmartTv::onPluginTimer); @@ -207,9 +204,7 @@ void IntegrationPluginLgSmartTv::setupThing(ThingSetupInfo *info) info->finish(Thing::ThingErrorNoError); return; - } - - if (thing->thingClassId() == webosTvThingClassId) { + } else if (thing->thingClassId() == webosTvThingClassId) { QHostAddress hostAddress = QHostAddress(thing->paramValue(webosTvThingHostAddressParamTypeId).toString()); WebosConnection *webosConnection = new WebosConnection(this); webosConnection->setHostAddress(hostAddress); @@ -262,7 +257,6 @@ void IntegrationPluginLgSmartTv::executeAction(ThingActionInfo *info) Action action = info->action(); if (thing->thingClassId() == lgSmartTvThingClassId) { - TvDevice * tvDevice = m_tvList.key(thing); if (!tvDevice->reachable()) { qCWarning(dcLgSmartTv) << "Device not reachable"; @@ -270,59 +264,79 @@ void IntegrationPluginLgSmartTv::executeAction(ThingActionInfo *info) } QNetworkReply *reply = nullptr; - - if (action.actionTypeId() == lgSmartTvCommandVolumeUpActionTypeId) { + if (action.actionTypeId() == lgSmartTvIncreaseVolumeActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::VolUp); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandVolumeDownActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvDecreaseVolumeActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::VolDown); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandMuteActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvMuteActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::Mute); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandChannelUpActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvCommandChannelUpActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::ChannelUp); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandChannelDownActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvCommandChannelDownActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::ChannelDown); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandPowerOffActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvCommandPowerOffActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::Power); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandArrowUpActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Up); + } else if (action.actionTypeId() == lgSmartTvNavigateActionTypeId) { + TvDevice::RemoteKey key; + QString navigateTo = action.paramValue(lgSmartTvNavigateActionToParamTypeId).toString(); + if (navigateTo == "up") { + key = TvDevice::Up; + } else if (navigateTo == "down") { + key = TvDevice::Down; + } else if (navigateTo == "left") { + key = TvDevice::Left; + } else if (navigateTo == "right") { + key = TvDevice::Right; + } else if (navigateTo == "enter") { + key = TvDevice::Ok; + } else if (navigateTo == "back") { + key = TvDevice::Back; + } else if (navigateTo == "menu") { + key = TvDevice::Menu; + } else if (navigateTo == "info") { + key = TvDevice::Info; + } else if (navigateTo == "home") { + key = TvDevice::Home; + } + QPair request = tvDevice->createPressButtonRequest(key); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandArrowDownActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Down); + } else if (action.actionTypeId() == lgSmartTvPlayActionTypeId) { + QPair request = tvDevice->createPressButtonRequest(TvDevice::Play); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandArrowLeftActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Left); + } else if (action.actionTypeId() == lgSmartTvPauseActionTypeId) { + QPair request = tvDevice->createPressButtonRequest(TvDevice::Pause); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandArrowRightActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Right); + } else if (action.actionTypeId() == lgSmartTvStopActionTypeId) { + QPair request = tvDevice->createPressButtonRequest(TvDevice::Stop); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandOkActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Ok); + } else if (action.actionTypeId() == lgSmartTvSkipNextActionTypeId) { + QPair request = tvDevice->createPressButtonRequest(TvDevice::SkipForward); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandBackActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Back); + } else if (action.actionTypeId() == lgSmartTvSkipBackActionTypeId) { + QPair request = tvDevice->createPressButtonRequest(TvDevice::SkipBackward); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandHomeActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Home); + } else if (action.actionTypeId() == lgSmartTvFastForwardActionTypeId) { + QPair request = tvDevice->createPressButtonRequest(TvDevice::FastForward); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandInputSourceActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvFastRewindActionTypeId) { + QPair request = tvDevice->createPressButtonRequest(TvDevice::Rewind); + reply = hardwareManager()->networkManager()->post(request.first, request.second); + } else if (action.actionTypeId() == lgSmartTvCommandInputSourceActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::ExternalInput); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandExitActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvCommandExitActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::Exit); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandInfoActionTypeId) { - QPair request = tvDevice->createPressButtonRequest(TvDevice::Info); - reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandMyAppsActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvCommandMyAppsActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::MyApps); reply = hardwareManager()->networkManager()->post(request.first, request.second); - } else if(action.actionTypeId() == lgSmartTvCommandProgramListActionTypeId) { + } else if (action.actionTypeId() == lgSmartTvCommandProgramListActionTypeId) { QPair request = tvDevice->createPressButtonRequest(TvDevice::ProgramList); reply = hardwareManager()->networkManager()->post(request.first, request.second); } @@ -347,61 +361,268 @@ void IntegrationPluginLgSmartTv::executeAction(ThingActionInfo *info) } } +void IntegrationPluginLgSmartTv::browseThing(BrowseResult *result) +{ + if (result->thing()->thingClassId() == lgSmartTvThingClassId) { + TvDevice *tv = m_tvList.key(result->thing()); + if (!tv) { + result->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + tv->browse(result); + } +} + +void IntegrationPluginLgSmartTv::browserItem(BrowserItemResult *result) +{ + if (result->thing()->thingClassId() == lgSmartTvThingClassId) { + TvDevice *tv = m_tvList.key(result->thing()); + if (!tv) { + result->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + tv->browserItem(result); + } +} + void IntegrationPluginLgSmartTv::pairTvDevice(Thing *thing) { - qCDebug(dcLgSmartTv()) << "Send pair request TV" << thing->name(); - QHostAddress host = QHostAddress(thing->paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); - int port = thing->paramValue(lgSmartTvThingPortParamTypeId).toInt(); + if (thing->thingClassId() == lgSmartTvThingClassId) { + qCDebug(dcLgSmartTv()) << "Send pair request TV" << thing->name(); + QHostAddress host = QHostAddress(thing->paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); + quint16 port = thing->paramValue(lgSmartTvThingPortParamTypeId).toUInt(); + quint16 eventHandlerPort = thing->paramValue(lgSmartTvThingEventHandlerPortParamTypeId).toUInt(); - pluginStorage()->beginGroup(thing->id().toString()); - QString key = pluginStorage()->value("key").toString(); - pluginStorage()->endGroup(); + pluginStorage()->beginGroup(thing->id().toString()); + QString key = pluginStorage()->value("key").toString(); + pluginStorage()->endGroup(); - QPair request = TvDevice::createPairingRequest(host, port, key); - QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); - connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, thing, [this, thing, reply]() { - TvDevice *tv = m_tvList.key(thing); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if(status != 200) { - qCWarning(dcLgSmartTv) << "Pair TV request error:" << status << reply->errorString(); - tv->setPaired(false); - } else { - qCDebug(dcLgSmartTv) << "Paired TV successfully."; - tv->setPaired(true); - refreshTv(thing); - } - }); + QPair request = TvDevice::createPairingRequest(host, port, eventHandlerPort, key); + QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); + connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, thing, [this, thing, reply]() { + TvDevice *tv = m_tvList.key(thing); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if(status != 200) { + qCWarning(dcLgSmartTv) << "Pair TV request error:" << status << reply->errorString(); + tv->setPaired(false); + } else { + qCDebug(dcLgSmartTv) << "Paired TV successfully."; + tv->setPaired(true); + loadAppList(thing); + + refreshTv(thing); + } + }); + } } void IntegrationPluginLgSmartTv::unpairTvDevice(Thing *thing) { - QHostAddress host = QHostAddress(thing->paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); - int port = thing->paramValue(lgSmartTvThingPortParamTypeId).toInt(); - QPair request = TvDevice::createEndPairingRequest(host, port); + if (thing->thingClassId() == lgSmartTvThingClassId) { + QHostAddress host = QHostAddress(thing->paramValue(lgSmartTvThingHostAddressParamTypeId).toString()); + quint16 port = thing->paramValue(lgSmartTvThingPortParamTypeId).toUInt(); + quint16 eventHandlerPort = thing->paramValue(lgSmartTvThingEventHandlerPortParamTypeId).toUInt(); + QPair request = TvDevice::createEndPairingRequest(host, port, eventHandlerPort); - QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); - connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - - connect(reply, &QNetworkReply::finished, this, [reply](){ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if(status != 200) { - qCWarning(dcLgSmartTv) << "End pairing TV (thing deleted) request error:" << status << reply->errorString(); - } else { - qCDebug(dcLgSmartTv) << "End pairing TV (thing deleted) successfully."; - } - }); + QNetworkReply *reply = hardwareManager()->networkManager()->post(request.first, request.second); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [reply](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if(status != 200) { + qCWarning(dcLgSmartTv) << "End pairing TV (thing deleted) request error:" << status << reply->errorString(); + } else { + qCDebug(dcLgSmartTv) << "End pairing TV (thing deleted) successfully."; + } + }); + } } void IntegrationPluginLgSmartTv::refreshTv(Thing *thing) { TvDevice *tv = m_tvList.key(thing); // check volume information - QNetworkReply *volumeReply = hardwareManager()->networkManager()->get(tv->createVolumeInformationRequest()); - connect(volumeReply, &QNetworkReply::finished, this, &IntegrationPluginLgSmartTv::onNetworkManagerReplyFinished); - m_volumeInfoRequests.insert(volumeReply, thing); + QNetworkReply *reply = hardwareManager()->networkManager()->get(tv->createVolumeInformationRequest()); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [=](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200) { + tv->setReachable(false); + qCWarning(dcLgSmartTv) << "Refresh tv information request error:" << status << reply->errorString(); + return; + } + if (!tv->reachable()) { + loadAppList(thing); + } + tv->setReachable(true); + tv->onVolumeInformationUpdate(reply->readAll()); + + // QByteArray data = reply->readAll(); + // qCDebug(dcLgSmartTv()) << "Refresh info received:" << data << qUtf8Printable(TvDevice::printXmlData(data)); + + + // QNetworkReply *reply = hardwareManager()->networkManager()->get(tv->createAppListRequest()); + // connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + // connect(reply, &QNetworkReply::finished, this, [reply, tv](){ + // int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // if (status != 200) { + // tv->setReachable(false); + // qCWarning(dcLgSmartTv) << "Refresh tv information request error:" << status << reply->errorString(); + // return; + // } + + // tv->setReachable(true); + // QByteArray data = reply->readAll(); + // qCDebug(dcLgSmartTv()) << "Refresh info received:" << data << qUtf8Printable(TvDevice::printXmlData(data)); + + + // tv->setReachable(true); + // tv->onVolumeInformationUpdate(reply->readAll()); + + // // Check channel information + // QNetworkReply *channelReply = hardwareManager()->networkManager()->get(tv->createChannelInformationRequest()); + // connect(channelReply, &QNetworkReply::finished, this, &IntegrationPluginLgSmartTv::onNetworkManagerReplyFinished); + // m_channelInfoRequests.insert(channelReply, thing); + // }); + }); + +} + +void IntegrationPluginLgSmartTv::loadAppList(Thing *thing) +{ + TvDevice *tv = m_tvList.key(thing); + tv->clearAppList(); + + // Note:: loading all apps (type 1) does not work, so load type 2 (premium) and typ 3 (myApps) sequentially + QNetworkReply *reply = hardwareManager()->networkManager()->get(tv->createAppListCountRequest(2)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [=](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200) { + tv->setReachable(false); + qCWarning(dcLgSmartTv) << "Get app count request error:" << status << reply->errorString(); + return; + } + + tv->setReachable(true); + + QByteArray data = reply->readAll(); + int appCount = 0; + QXmlStreamReader xml(data); + while (!xml.atEnd() && !xml.hasError()) { + xml.readNext(); + if (xml.name() == "number") { + appCount = QVariant(xml.readElementText()).toInt(); + } + } + + QNetworkReply *reply = hardwareManager()->networkManager()->get(tv->createAppListRequest(2, appCount)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [=](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200) { + tv->setReachable(false); + qCWarning(dcLgSmartTv) << "Get app list request error:" << status << reply->errorString(); + return; + } + + QByteArray data = reply->readAll(); + + // Parse app list + QVariantList appList; + QXmlStreamReader xml(qUtf8Printable(QString::fromLatin1(data))); + while (xml.readNextStartElement() && !xml.atEnd()) { + if (xml.name() == "dataList") { + while (xml.readNextStartElement()) { + QVariantMap appMap; + if (xml.name() == "data") { + qCDebug(dcLgSmartTv()) << "--------------------"; + while (xml.readNextStartElement()) { + qCDebug(dcLgSmartTv()) << "-->" << xml.name() << xml.readElementText(); + if (xml.name() == "name") { + appMap.insert("name", xml.readElementText()); + } else if (xml.name() == "icon") { + appMap.insert("icon", xml.readElementText()); + } if (xml.name() == "cpid") { + appMap.insert("cpid", xml.readElementText()); + } + } + } + appList.append(appMap); + } + } + } + + qCDebug(dcLgSmartTv()) << "Adding app list" << appList; + tv->addAppList(appList); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(tv->createAppListCountRequest(3)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [=](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200) { + tv->setReachable(false); + qCWarning(dcLgSmartTv) << "Get app count request error:" << status << reply->errorString(); + return; + } + + QByteArray data = reply->readAll(); + + int appCount = 0; + QXmlStreamReader xml(data); + while (!xml.atEnd() && !xml.hasError()) { + xml.readNext(); + if (xml.name() == "number") { + appCount = QVariant(xml.readElementText()).toInt(); + } + } + + QNetworkReply *reply = hardwareManager()->networkManager()->get(tv->createAppListRequest(3, appCount)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [reply, tv](){ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200) { + tv->setReachable(false); + qCWarning(dcLgSmartTv) << "Refresh tv information request error:" << status << reply->errorString(); + return; + } + + QByteArray data = reply->readAll(); + + // Parse app list + QVariantList appList; + QXmlStreamReader xml(QString::fromLatin1(data)); + while (xml.readNextStartElement() && !xml.atEnd()) { + if (xml.name() == "dataList") { + while (xml.readNextStartElement()) { + if (xml.name() == "data") { + QVariantMap appMap; + while (xml.readNextStartElement()) { + if (xml.name() == "name") { + appMap.insert("name", xml.readElementText()); + } else if (xml.name() == "icon") { + appMap.insert("icon", xml.readElementText()); + } if (xml.name() == "auid") { + appMap.insert("auid", xml.readElementText()); + } + } + qCDebug(dcLgSmartTv()) << appMap; + appList.append(appMap); + } + } + } + } + + qCDebug(dcLgSmartTv()) << "Adding app list" << appList; + tv->addAppList(appList); + }); + }); + }); + }); } void IntegrationPluginLgSmartTv::onPluginTimer() @@ -426,7 +647,7 @@ void IntegrationPluginLgSmartTv::onNetworkManagerReplyFinished() if (m_volumeInfoRequests.keys().contains(reply)) { Thing *thing = m_volumeInfoRequests.take(reply); TvDevice *tv = m_tvList.key(thing); - if(status != 200) { + if (status != 200) { tv->setReachable(false); qCWarning(dcLgSmartTv) << "Volume information request error:" << status << reply->errorString(); if (status == 401) { @@ -445,7 +666,7 @@ void IntegrationPluginLgSmartTv::onNetworkManagerReplyFinished() } else if (m_channelInfoRequests.keys().contains(reply)) { Thing *thing = m_channelInfoRequests.take(reply); TvDevice *tv = m_tvList.key(thing); - if(status != 200) { + if (status != 200) { tv->setReachable(false); qCWarning(dcLgSmartTv) << "Channel information request error:" << status << reply->errorString(); if (status == 401) { @@ -466,8 +687,8 @@ void IntegrationPluginLgSmartTv::stateChanged() thing->setStateValue(lgSmartTvConnectedStateTypeId, tvDevice->reachable()); thing->setStateValue(lgSmartTvTv3DModeStateTypeId, tvDevice->is3DMode()); - thing->setStateValue(lgSmartTvTvVolumeLevelStateTypeId, tvDevice->volumeLevel()); - thing->setStateValue(lgSmartTvTvMuteStateTypeId, tvDevice->mute()); + thing->setStateValue(lgSmartTvVolumeLevelStateTypeId, tvDevice->volumeLevel()); + thing->setStateValue(lgSmartTvMuteStateTypeId, tvDevice->mute()); thing->setStateValue(lgSmartTvTvChannelTypeStateTypeId, tvDevice->channelType()); thing->setStateValue(lgSmartTvTvChannelNameStateTypeId, tvDevice->channelName()); thing->setStateValue(lgSmartTvTvChannelNumberStateTypeId, tvDevice->channelNumber()); diff --git a/lgsmarttv/integrationpluginlgsmarttv.h b/lgsmarttv/integrationpluginlgsmarttv.h index 025b0d3f..7299772f 100644 --- a/lgsmarttv/integrationpluginlgsmarttv.h +++ b/lgsmarttv/integrationpluginlgsmarttv.h @@ -61,6 +61,9 @@ public: void executeAction(ThingActionInfo *info) override; + void browseThing(BrowseResult *result) override; + void browserItem(BrowserItemResult *result) override; + private: PluginTimer *m_pluginTimer = nullptr; QHash m_tvList; @@ -76,6 +79,8 @@ private: void unpairTvDevice(Thing *thing); void refreshTv(Thing *thing); + void loadAppList(Thing *thing); + private slots: void onPluginTimer(); void onNetworkManagerReplyFinished(); diff --git a/lgsmarttv/integrationpluginlgsmarttv.json b/lgsmarttv/integrationpluginlgsmarttv.json index cf8eba8b..49dbd31c 100644 --- a/lgsmarttv/integrationpluginlgsmarttv.json +++ b/lgsmarttv/integrationpluginlgsmarttv.json @@ -14,41 +14,51 @@ "displayName": "LG Smart Tv", "createMethods": [ "discovery" ], "setupMethod": "displayPin", - "interfaces": ["connectable"], + "browsable": true, + "interfaces": ["mediacontroller", "navigationpad", "volumecontroller", "connectable"], "paramTypes": [ { "id": "797a1915-cb85-400a-9c1a-db09c73b54d5", "name": "name", - "displayName": "name", + "displayName": "Name", "type": "QString", "inputType": "TextLine" }, { "id": "0deec372-b955-49d2-9906-fc1a603dd637", "name": "model", - "displayName": "model", + "displayName": "Model", "type": "QString", "inputType": "TextLine" }, { "id": "0af152bb-8844-44d8-afee-d08bb0e28558", "name": "uuid", - "displayName": "uuid", + "displayName": "UUID", "type": "QString", "inputType": "TextLine" }, { "id": "9710c07c-0725-4099-bf73-2513efbc0872", "name": "hostAddress", - "displayName": "host address", + "displayName": "Host address", "type": "QString", - "inputType": "IPv4Address" + "inputType": "IPv4Address", + "defaultValue": "127.0.0.1" }, { "id": "442abce0-16f9-4628-9534-e98e7f1c0a51", "name": "port", - "displayName": "port", - "type": "int" + "displayName": "Port", + "type": "uint", + "defaultValue": 8080 + }, + { + "id": "2904dc2d-d734-4893-ab69-222cd7a5d147", + "name": "eventHandlerPort", + "displayName": "Event handler port", + "type": "uint", + "defaultValue": 9000 } ], "stateTypes": [ @@ -69,21 +79,24 @@ "defaultValue": false }, { - "id": "07d39a6e-7eab-42d0-851d-9f3bcd3bbb57", - "name": "tvVolumeLevel", - "displayName": "volume", - "displayNameEvent": "Volume changed", - "type": "int", - "unit": "Percentage", - "defaultValue": 0 + "id": "e1ead9b0-2db5-41bd-aedb-b78a99235dde", + "name": "mute", + "displayName": "Mute", + "displayNameEvent": "Mute changed", + "displayNameAction": "Set mute", + "type": "bool", + "defaultValue": false, + "writable": true }, { - "id": "a6ac9061-3de7-403a-a646-790ca5d73764", - "name": "tvMute", - "displayNameEvent": "mute changed", - "displayName": "mute", - "type": "bool", - "defaultValue": false + "id": "597f9e07-108e-4345-80ab-1842bcb40666", + "displayName": "Volume", + "displayNameEvent": "Volume changed", + "maxValue": 100, + "minValue": 0, + "name": "volumeLevel", + "type": "int", + "defaultValue": 10 }, { "id": "84c86670-77c7-4fc6-9e23-abca066e76aa", @@ -137,73 +150,87 @@ "actionTypes": [ { "id": "ac5d7dcd-dfe8-4a94-9ab9-21b3f804b39e", - "name": "commandVolumeUp", - "displayName": "volume up" + "name": "increaseVolume", + "displayName": "Volume +" }, { "id": "62b17bec-f461-4ffa-93d1-67a9430d55e1", - "name": "commandVolumeDown", - "displayName": "volume down" - }, - { - "id": "1aa9d7f0-0f66-4b90-bb72-f6b7b2118221", - "name": "commandMute", - "displayName": "mute" - }, - { - "id": "b7e31999-ba67-443d-8e5c-ec104af987bd", - "name": "commandUnmute", - "displayName": "unmute" + "name": "decreaseVolume", + "displayName": "Volume -" }, { "id": "acd1f6a0-2cfa-4665-9607-cf94245ec5a3", "name": "commandChannelUp", - "displayName": "channel up" + "displayName": "Channel up" }, { "id": "6ea66772-0e6d-40b1-978c-a01fb53871dd", "name": "commandChannelDown", - "displayName": "channel down" + "displayName": "Channel down" }, { "id": "cbe41134-ff11-4916-815b-3ac289c64090", "name": "commandPowerOff", - "displayName": "power off" + "displayName": "Power off" }, { - "id": "57c483b4-4ddf-4470-828c-8d8767e7a923", - "name": "commandArrowUp", - "displayName": "arrow up" + "id": "193f80bf-3c9a-47a4-b54e-7cecf0bf0f45", + "name": "navigate", + "displayName": "Navigate", + "paramTypes": [ + { + "id": "93861dac-0c24-4a3b-903d-d1be44eae611", + "name": "to", + "displayName": "to", + "type": "QString", + "allowedValues": [ + "up", + "down", + "left", + "right", + "enter", + "back", + "menu", + "info", + "home" + ] + } + ] }, { - "id": "614cf1af-5cf7-4bb2-885c-4414078d8899", - "name": "commandArrowDown", - "displayName": "arrow down" + "id": "2c1e07e1-198a-45f4-9c58-2c5e31290262", + "name": "skipBack", + "displayName": "skip back" }, { - "id": "916394dd-7833-4875-8d7a-49d7d24ceeb2", - "name": "commandArrowLeft", - "displayName": "arrow left" + "id": "db984804-acad-4905-bc89-9ebcdf64478e", + "name": "fastRewind", + "displayName": "rewind" }, { - "id": "01e3df1e-638b-4e14-ba85-660267766062", - "name": "commandArrowRight", - "displayName": "arrow right" + "id": "590c0f43-be86-4692-9b89-13730e71bed4", + "name": "stop", + "displayName": "stop" }, { - "id": "257dfa59-0d38-4e18-a3fc-213809fdb12f", - "name": "commandOk", - "displayName": "OK" + "id": "17f5c9d4-e060-4daa-a039-3cd7eb6ef8bf", + "name": "play", + "displayName": "play" }, { - "id": "ce4184b3-6b8e-4fc3-a4cb-7b8ec72f2ce9", - "name": "commandBack", - "displayName": "back" + "id": "6360fe9e-00c6-4d32-bcfc-69b5b5d85f8b", + "name": "pause", + "displayName": "pause" }, { - "id": "33f941c1-f5fc-4449-b6e3-93eafca493e0", - "name": "commandHome", - "displayName": "home" + "id": "48c7afb6-8252-4c11-aff9-e4e4954b2d30", + "name": "fastForward", + "displayName": "fastForward" + }, + { + "id": "353ad30a-25b7-4ae9-b47b-057bf101f8d1", + "name": "skipNext", + "displayName": "skipNext" }, { "id": "9a6e5111-95d3-49ac-8056-249e704b1509", @@ -215,11 +242,6 @@ "name": "commandExit", "displayName": "exit" }, - { - "id": "9c1290d5-3135-4124-a576-fc7522cffdcf", - "name": "commandInfo", - "displayName": "info" - }, { "id": "47d65cac-fe75-4c36-9dee-9862c1c1130e", "name": "commandMyApps", @@ -238,7 +260,7 @@ "displayName": "LG WebOS Smart Tv", "createMethods": [ "Discovery" ], "setupMethod": "PushButton", - "interfaces": [ "extendedvolumecontroller", "mediacontroller", "connectable" ], + "interfaces": [ "volumecontroller", "mediacontroller", "connectable" ], "paramTypes": [ { "id": "adebba0c-ac7b-4b6a-8668-307f44dbe5bf", @@ -288,6 +310,16 @@ } ], "actionTypes": [ + { + "id": "ff814f79-5c79-4346-94fa-4da1a6f95c82", + "displayName": "Volume +", + "name": "increaseVolume" + }, + { + "id": "360326c4-c6c3-4304-b539-aca08f711c8a", + "displayName": "Volume -", + "name": "decreaseVolume" + }, { "id": "35cdb587-956a-4310-8e4c-85fdc240943e", "displayName": "Skip back", diff --git a/lgsmarttv/tvdevice.cpp b/lgsmarttv/tvdevice.cpp index 13c8c76b..f33afbf9 100644 --- a/lgsmarttv/tvdevice.cpp +++ b/lgsmarttv/tvdevice.cpp @@ -31,20 +31,32 @@ #include "tvdevice.h" #include "extern-plugininfo.h" -TvDevice::TvDevice(const QHostAddress &hostAddress, const int &port, QObject *parent) : +TvDevice::TvDevice(const QHostAddress &hostAddress, quint16 port, quint16 eventHandlerPort, QObject *parent) : QObject(parent), m_hostAddress(hostAddress), m_port(port), - m_paired(false), - m_reachable(false), - m_is3DMode(false), - m_mute(false), - m_volumeLevel(-1), - m_inputSourceIndex(-1), - m_channelNumber(-1) + m_eventHandlerPort(eventHandlerPort) { - m_eventHandler = new TvEventHandler(hostAddress, port, this); + m_eventHandler = new TvEventHandler(hostAddress, eventHandlerPort, this); connect(m_eventHandler, &TvEventHandler::eventOccured, this, &TvDevice::eventOccured); + + // Init FS + m_virtualFs = new VirtualFsNode(BrowserItem()); + + // Apps + BrowserItem item = BrowserItem("apps", tr("Apps"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconApplication); + VirtualFsNode *apps = new VirtualFsNode(item); + m_virtualFs->addChild(apps); + + // Channels + item = BrowserItem("channels", tr("Channels"), true); + item.setDescription(tr("")); + item.setIcon(BrowserItem::BrowserIconVideo); + VirtualFsNode *channels = new VirtualFsNode(item); + m_virtualFs->addChild(channels); + } void TvDevice::setKey(const QString &key) @@ -57,26 +69,21 @@ QString TvDevice::key() const return m_key; } -void TvDevice::setHostAddress(const QHostAddress &hostAddress) -{ - m_hostAddress = hostAddress; -} - QHostAddress TvDevice::hostAddress() const { return m_hostAddress; } -void TvDevice::setPort(const int &port) -{ - m_port = port; -} - -int TvDevice::port() const +quint16 TvDevice::port() const { return m_port; } +quint16 TvDevice::eventHandlerPort() const +{ + return m_eventHandlerPort; +} + void TvDevice::setUuid(const QString &uuid) { m_uuid = uuid; @@ -91,7 +98,7 @@ void TvDevice::setPaired(const bool &paired) { if (m_paired != paired) { m_paired = paired; - stateChanged(); + emit stateChanged(); } } @@ -159,7 +166,7 @@ QString TvDevice::inputSourceLabelName() const return m_inputSourceLabel; } -QPair TvDevice::createDisplayKeyRequest(const QHostAddress &host, const int &port) +QPair TvDevice::createDisplayKeyRequest(const QHostAddress &host, quint16 port) { QString urlString = "http://" + host.toString() + ":" + QString::number(port) + "/udap/api/pairing"; QNetworkRequest request; @@ -171,7 +178,7 @@ QPair TvDevice::createDisplayKeyRequest(const QHost return QPair(request, data); } -QPair TvDevice::createPairingRequest(const QHostAddress &host, const int &port, const QString &key) +QPair TvDevice::createPairingRequest(const QHostAddress &host, quint16 port, quint16 eventHandlerPort, const QString &key) { QString urlString = "http://" + host.toString() + ":" + QString::number(port) + "/udap/api/pairing"; QNetworkRequest request; @@ -179,23 +186,12 @@ QPair TvDevice::createPairingRequest(const QHostAdd request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml; charset=utf-8")); request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0 nymea")); - QByteArray data = "hello" + key.toUtf8() + "8080"; + QByteArray data = "hello" + + key.toUtf8() + "" + QByteArray::number(eventHandlerPort) + ""; return QPair(request, data); } -QPair TvDevice::createEndPairingRequest(const QUrl &url) -{ - QNetworkRequest request; - request.setUrl(url); - request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml; charset=utf-8")); - request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0 nymea")); - request.setRawHeader("Connection", "Close"); - - QByteArray data = "byebye8080"; - return QPair(request, data); -} - -QPair TvDevice::createEndPairingRequest(const QHostAddress &host, const int &port) +QPair TvDevice::createEndPairingRequest(const QHostAddress &host, quint16 port, quint16 eventHandlerPort) { QString urlString = "http://" + host.toString() + ":" + QString::number(port) + "/udap/api/pairing"; QNetworkRequest request; @@ -204,11 +200,11 @@ QPair TvDevice::createEndPairingRequest(const QHost request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0 nymea")); request.setRawHeader("Connection", "Close"); - QByteArray data = "byebye8080"; + QByteArray data = "byebye" + QByteArray::number(eventHandlerPort) + ""; return QPair(request, data); } -QPair TvDevice::createEventRequest(const QHostAddress &host, const int &port) +QPair TvDevice::createEventRequest(const QHostAddress &host, quint16 port, quint16 eventHandlerPort) { QString urlString = "http://" + host.toString() + ":" + QString::number(port) + "/udap/api/event"; QNetworkRequest request; @@ -217,16 +213,54 @@ QPair TvDevice::createEventRequest(const QHostAddre request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0 nymea")); request.setRawHeader("Connection", "Close"); - QByteArray data = "byebye8080"; + QByteArray data = "byebye" + QByteArray::number(eventHandlerPort) + ""; return QPair(request, data); } +QUrl TvDevice::buildDisplayImageUrl() const +{ + return QUrl("http://" + m_hostAddress.toString() + ":" + QString::number(m_port) + "/udap/api/data?target=screen_image"); +} + QNetworkRequest TvDevice::createVolumeInformationRequest() { QString urlString = "http://" + hostAddress().toString() + ":" + QString::number(port()) + "/udap/api/data?target=volume_info"; QNetworkRequest request; request.setUrl(QUrl(urlString)); - request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml")); + request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml; charset=utf-8")); + request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0")); + request.setRawHeader("Connection", "Close"); + return request; +} + +QNetworkRequest TvDevice::createChannelListRequest() +{ + QString urlString = "http://" + hostAddress().toString() + ":" + QString::number(port()) + "/udap/api/data?target=channel_list"; + QNetworkRequest request; + request.setUrl(QUrl(urlString)); + request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml; charset=utf-8")); + request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0")); + request.setRawHeader("Connection", "Close"); + return request; +} + +QNetworkRequest TvDevice::createAppListCountRequest(int type) +{ + QString urlString = "http://" + hostAddress().toString() + ":" + QString::number(port()) + "/udap/api/data?target=appnum_get&type=" + QString::number(type); + QNetworkRequest request; + request.setUrl(QUrl(urlString)); + request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml; charset=utf-8")); + request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0")); + request.setRawHeader("Connection", "Close"); + return request; +} + +QNetworkRequest TvDevice::createAppListRequest(int type, int appCount) +{ + QString urlString = "http://" + hostAddress().toString() + ":" + QString::number(port()) + "/udap/api/data?target=applist_get&type=" + QString::number(type) + "&index=1&number=" + QString::number(appCount); + QNetworkRequest request; + request.setUrl(QUrl(urlString)); + request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml; charset=utf-8")); request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0")); request.setRawHeader("Connection", "Close"); return request; @@ -237,7 +271,7 @@ QNetworkRequest TvDevice::createChannelInformationRequest() QString urlString = "http://" + hostAddress().toString() + ":" + QString::number(port()) + "/udap/api/data?target=cur_channel"; QNetworkRequest request; request.setUrl(QUrl(urlString)); - request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml")); + request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/xml; charset=utf-8")); request.setHeader(QNetworkRequest::UserAgentHeader,QVariant("UDAP/2.0")); request.setRawHeader("Connection", "Close"); return request; @@ -262,13 +296,13 @@ void TvDevice::onVolumeInformationUpdate(const QByteArray &data) { //qCDebug(dcLgSmartTv()) << printXmlData(data); QXmlStreamReader xml(data); - while(!xml.atEnd() && !xml.hasError()) { + while (!xml.atEnd() && !xml.hasError()) { xml.readNext(); - if(xml.name() == "mute") { + if (xml.name() == "mute") { m_mute = QVariant(xml.readElementText()).toBool(); } - if(xml.name() == "level") { + if (xml.name() == "level") { m_volumeLevel = QVariant(xml.readElementText()).toInt(); } } @@ -280,56 +314,121 @@ void TvDevice::onChannelInformationUpdate(const QByteArray &data) //qCDebug(dcLgSmartTv()) << printXmlData(data); QXmlStreamReader xml(data); - while(!xml.atEnd() && !xml.hasError()) { + while (!xml.atEnd() && !xml.hasError()) { xml.readNext(); - if(xml.name() == "chtype") { + if (xml.name() == "chtype") { m_channelType = xml.readElementText(); } - if(xml.name() == "major") { + if (xml.name() == "major") { m_channelNumber = QVariant(xml.readElementText()).toInt(); } - if(xml.name() == "chname") { + if (xml.name() == "chname") { m_channelName = xml.readElementText(); } - if(xml.name() == "progName") { + if (xml.name() == "progName") { m_programName = xml.readElementText(); } - if(xml.name() == "inputSourceIdx") { + if (xml.name() == "inputSourceIdx") { m_inputSourceIndex = QVariant(xml.readElementText()).toInt(); } - if(xml.name() == "labelName") { + if (xml.name() == "labelName") { m_inputSourceLabel = xml.readElementText(); } } emit stateChanged(); } +void TvDevice::onAppListUpdated(const QByteArray &appList) +{ + Q_UNUSED(appList) +} + QString TvDevice::printXmlData(const 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()) { - qCWarning(dcLgSmartTv()) << "error reading XML thing information:" << reader.errorString(); + if (reader.hasError()) { + qCWarning(dcLgSmartTv()) << "Error reading XML thing information:" << reader.errorString(); } return xmlOut; } +void TvDevice::browse(BrowseResult *result) +{ + qCDebug(dcLgSmartTv()) << "Browse" << result->itemId(); + VirtualFsNode *node = m_virtualFs->findNode(result->itemId()); + if (node) { + if (result->itemId().isEmpty()) { + foreach (VirtualFsNode *child, node->childs) { + result->addItem(child->item); + } + result->finish(Thing::ThingErrorNoError); + return; + } + + if (result->itemId().startsWith("apps")) { + qCDebug(dcLgSmartTv()) << m_appList; + // Build the app list + foreach (const QVariant &appVariant, m_appList) { + QVariantMap appMap = appVariant.toMap(); + qCDebug(dcLgSmartTv()) << "Entry:" << appMap; + BrowserItem item("apps:" + appMap.value("name").toString(), appMap.value("name").toString()); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconApplication); + result->addItem(item); + } + result->finish(Thing::ThingErrorNoError); + + } else if (result->itemId().startsWith("channels")) { + // Build the channel list + + } + } + + +} + +void TvDevice::browserItem(BrowserItemResult *result) +{ + qCDebug(dcLgSmartTv()) << "Browse item" << result->itemId(); + +} + +int TvDevice::launchBrowserItem(const QString &itemId) +{ + qCDebug(dcLgSmartTv()) << "Launch browse item" << itemId; + + // TODO: switch channel or launch app + return 0; +} + +void TvDevice::clearAppList() +{ + m_appList.clear(); +} + +void TvDevice::addAppList(const QVariantList &appList) +{ + foreach (const QVariant &appVariant, appList) { + m_appList.append(appVariant); + } +} + void TvDevice::eventOccured(const QByteArray &data) { - qCDebug(dcLgSmartTv()) << "Event handler data received" << printXmlData(data); + qCDebug(dcLgSmartTv()) << "Event handler data received" << qUtf8Printable(printXmlData(data)); // if we got a channel changed event... - if(data.contains("ChannelChanged")) { + if (data.contains("ChannelChanged")) { onChannelInformationUpdate(data); return; } @@ -338,7 +437,7 @@ void TvDevice::eventOccured(const QByteArray &data) // if the tv suspends, it will send a byebye message, which means // the pairing will be closed. - if(data.contains("api type=\"pairing\"") && data.contains("byebye")) { + if (data.contains("api type=\"pairing\"") && data.contains("byebye")) { qCDebug(dcLgSmartTv()) << "Ended pairing (host)"; setPaired(false); setReachable(false); @@ -347,14 +446,12 @@ void TvDevice::eventOccured(const QByteArray &data) // check if this is a 3DMode changed event QXmlStreamReader xml(data); - - while(!xml.atEnd() && !xml.hasError()) { + while (!xml.atEnd() && !xml.hasError()) { xml.readNext(); - - if(xml.name() == "name") { - if(xml.readElementText() == "3DMode") { + if (xml.name() == "name") { + if (xml.readElementText() == "3DMode") { xml.readNext(); - if(xml.name() == "value") { + if (xml.name() == "value") { m_is3DMode = QVariant(xml.readElementText()).toBool(); } } diff --git a/lgsmarttv/tvdevice.h b/lgsmarttv/tvdevice.h index c1fb917a..997c4c4e 100644 --- a/lgsmarttv/tvdevice.h +++ b/lgsmarttv/tvdevice.h @@ -50,7 +50,7 @@ class TvDevice : public QObject { Q_OBJECT public: - explicit TvDevice(const QHostAddress &hostAddress, const int &port, QObject *parent = 0); + explicit TvDevice(const QHostAddress &hostAddress, quint16 port, quint16 eventHandlerPort, QObject *parent = 0); enum RemoteKey{ Power = 1, @@ -125,11 +125,9 @@ public: void setKey(const QString &key); QString key() const; - void setHostAddress(const QHostAddress &hostAddress); QHostAddress hostAddress() const; - - void setPort(const int &port); - int port() const; + quint16 port() const; + quint16 eventHandlerPort() const; void setUuid(const QString &uuid); QString uuid() const; @@ -152,41 +150,78 @@ public: QString inputSourceLabelName() const; // other methods - static QPair createDisplayKeyRequest(const QHostAddress &host, const int &port); - static QPair createPairingRequest(const QHostAddress &host, const int &port, const QString &key); - static QPair createEndPairingRequest(const QUrl &url); - static QPair createEndPairingRequest(const QHostAddress &host, const int &port); - static QPair createEventRequest(const QHostAddress &host, const int &port); + static QPair createDisplayKeyRequest(const QHostAddress &host, quint16 port); + static QPair createPairingRequest(const QHostAddress &host, quint16 port, quint16 eventHandlerPort, const QString &key); + static QPair createEndPairingRequest(const QHostAddress &host, quint16 port, quint16 eventHandlerPort); + static QPair createEventRequest(const QHostAddress &host, quint16 port, quint16 eventHandlerPort); QPair createPressButtonRequest(const TvDevice::RemoteKey &key); + QUrl buildDisplayImageUrl() const; + QNetworkRequest createVolumeInformationRequest(); QNetworkRequest createChannelInformationRequest(); + QNetworkRequest createChannelListRequest(); + QNetworkRequest createAppListCountRequest(int type); + QNetworkRequest createAppListRequest(int type, int appCount); + void onVolumeInformationUpdate(const QByteArray &data); void onChannelInformationUpdate(const QByteArray &data); + void onAppListUpdated(const QByteArray &appList); + + static QString printXmlData(const QByteArray &data); + + void browse(BrowseResult *result); + void browserItem(BrowserItemResult *result); + int launchBrowserItem(const QString &itemId); + + void clearAppList(); + void addAppList(const QVariantList &appList); private: TvEventHandler *m_eventHandler; QHostAddress m_hostAddress; - int m_port; + quint16 m_port = 0; + quint16 m_eventHandlerPort = 0; QString m_uuid; QString m_key; + QVariantList m_appList; + // States - bool m_paired; - bool m_reachable; - bool m_is3DMode; - bool m_mute; - int m_volumeLevel; - int m_inputSourceIndex; - int m_channelNumber; + bool m_paired = false; + bool m_reachable = false; + bool m_is3DMode = false; + bool m_mute = false; + int m_volumeLevel = 20; + int m_inputSourceIndex = -1; + int m_channelNumber = -1; QString m_channelType; QString m_channelName; QString m_programName; QString m_inputSourceLabel; - QString printXmlData(const QByteArray &data); + class VirtualFsNode { + public: + VirtualFsNode(const BrowserItem &item):item(item) {} + ~VirtualFsNode() { qDeleteAll(childs); } + BrowserItem item; + QList childs; + QString getMethod; + QVariantMap getParams; + void addChild(VirtualFsNode* child) {childs.append(child); } + VirtualFsNode *findNode(const QString &id) { + if (item.id() == id) return this; + foreach (VirtualFsNode *child, childs) { + VirtualFsNode *node = child->findNode(id); + if (node) return node; + } + return nullptr; + } + }; + VirtualFsNode* m_virtualFs = nullptr; + signals: void stateChanged(); diff --git a/lgsmarttv/tveventhandler.cpp b/lgsmarttv/tveventhandler.cpp index 6ce827f2..ff38a94e 100644 --- a/lgsmarttv/tveventhandler.cpp +++ b/lgsmarttv/tveventhandler.cpp @@ -31,20 +31,41 @@ #include "tveventhandler.h" #include "extern-plugininfo.h" -TvEventHandler::TvEventHandler(const QHostAddress &host, const int &port, QObject *parent) : +TvEventHandler::TvEventHandler(const QHostAddress &host, quint16 eventHandlerPort, QObject *parent) : QTcpServer(parent), m_host(host), - m_port(port), + m_port(eventHandlerPort), m_expectingData(false) { - listen(QHostAddress::AnyIPv4, m_port); + if (!listen(QHostAddress::AnyIPv4, m_port)) { + qCWarning(dcLgSmartTv()) << "EventHandler: Could not listen for events on" << m_port; + } else { + qCDebug(dcLgSmartTv()) << "EventHandler: listening on port" << m_port; + } +} + +uint TvEventHandler::getFreePort() +{ + uint port = 9000; + QTcpServer testServer; + for (int i = 0; i < 100; i++) { + if (testServer.listen(QHostAddress::AnyIPv4, port)) { + qCDebug(dcLgSmartTv()) << "EventHandler: found free port" << port; + testServer.close(); + return port; + } else { + qCDebug(dcLgSmartTv()) << "EventHandler: port not free" << port << "picking next one..."; + } + } + + return port; } void TvEventHandler::incomingConnection(qintptr socket) { QTcpSocket* tcpSocket = new QTcpSocket(this); tcpSocket->setSocketDescriptor(socket); - qCDebug(dcLgSmartTv()) << "Event handler -> incoming connection" << tcpSocket->peerAddress().toString() << tcpSocket->peerName(); + qCDebug(dcLgSmartTv()) << "EventHandler: incoming connection" << tcpSocket->peerAddress().toString() << tcpSocket->peerName(); connect(tcpSocket, &QTcpSocket::readyRead, this, &TvEventHandler::readClient); connect(tcpSocket, &QTcpSocket::disconnected, this, &TvEventHandler::onDisconnected); } @@ -53,22 +74,22 @@ void TvEventHandler::readClient() { QTcpSocket* socket = (QTcpSocket*)sender(); - // reject everything, except the tv + // Reject everything, except data from the TV if(socket->peerAddress() != m_host){ socket->close(); socket->deleteLater(); - qCWarning(dcLgSmartTv()) << "Event handler -> rejecting connection from " << socket->peerAddress().toString(); + qCWarning(dcLgSmartTv()) << "EventHandler: rejecting connection from " << socket->peerAddress().toString(); return; } // the tv sends first the header (POST /udap/api/.... HTTP/1.1) // in the scond package the tv sends the information (xml format) - while(!socket->atEnd()){ + while (!socket->atEnd()){ QByteArray data = socket->readAll(); // check if we got information - if(data.startsWith(" event occured" << "http://" << m_host.toString() << ":" << m_port << tokens[1]; + qCDebug(dcLgSmartTv()) << "EventHandler: event occured" << "http://" << m_host.toString() << ":" << m_port << tokens[1]; } } } @@ -94,7 +115,7 @@ void TvEventHandler::readClient() void TvEventHandler::onDisconnected() { QTcpSocket* socket = (QTcpSocket*)sender(); - qCDebug(dcLgSmartTv()) << "event handler -> client disconnected" << socket->peerAddress(); + qCDebug(dcLgSmartTv()) << "EventHandler: client disconnected" << socket->peerAddress(); socket->deleteLater(); } diff --git a/lgsmarttv/tveventhandler.h b/lgsmarttv/tveventhandler.h index 188ef74e..3e0d09bc 100644 --- a/lgsmarttv/tveventhandler.h +++ b/lgsmarttv/tveventhandler.h @@ -45,14 +45,16 @@ class TvEventHandler : public QTcpServer { Q_OBJECT public: - explicit TvEventHandler(const QHostAddress &host, const int &port = 8080, QObject *parent = 0); + explicit TvEventHandler(const QHostAddress &host, quint16 eventHandlerPort = 9000, QObject *parent = 0); + + static uint getFreePort(); protected: void incomingConnection(qintptr socket) override; private: QHostAddress m_host; - int m_port; + quint16 m_port; bool m_expectingData; signals: