From 8a074e3c37c7e7d77b4c54b479a9361a2b4f1f45 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 17 Nov 2019 00:53:24 +0100 Subject: [PATCH] Kodi: Automatically redetect Kodi when its IP address changes --- kodi/README.md | 29 ++++++--------- kodi/devicepluginkodi.cpp | 78 ++++++++++++++++++++++++++++++++------- kodi/devicepluginkodi.h | 4 ++ 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/kodi/README.md b/kodi/README.md index 1e5037fc..2288d959 100644 --- a/kodi/README.md +++ b/kodi/README.md @@ -1,26 +1,21 @@ # Kodi -This plugin allows you to controll the media center [Kodi](http://kodi.tv/). If you want to discover -and control Kodi with nymea, you need to activate the remote access and the UPnP service. +This plugin allows to integrate nymea with the [Kodi media center](http://kodi.tv/). The minimum requred version of +Kodi is 13 (Gotham). -## Activate Zeroconf +## Setup -In order to discover Kodi in the network, you need to activate the zeroconf serive in the Kodi settings: +Is is required to enable the following settings in Kodi: -### Settings +Navigate to Settings -> Services -> Control and activate "Alow Remote control via HTTP". -![Settings](https://raw.githubusercontent.com/guh/nymea-plugins/master/kodi/docs/images/kodi_settings.png "Kodi settings) +If nymea and Kodi are installed on the same system, activate "Allow remote control from applications on this system" or if +kodi is installed on a different system in the same network, activate "Allow remote control from applications on other systems". -### Settings - Services +In addition, it is recommended to activate "Announce services to other systems" to allow nymea discovery the kodi setup automatically. -![Services](https://raw.githubusercontent.com/guh/nymea-plugins/master/kodi/docs/images/kodi_services.png "Kodi services) +Once those settings are activated, the kodi system can be added to nymea. -Activate zeroconf. - -## Activate "Remote Control" -In order to control Kodi over the network with nymea, you need to activate the remote control permissions: - -### Settings - Services - Remote Control -Activate all options. - -![Reote](https://raw.githubusercontent.com/guh/nymea-plugins/master/kodi/docs/images/kodi_remote.png "Kodi Remote) +Note: If ZeroConf cannot be used, the device can be added manually and at least the IP, Port and HTTP Port parameters must be given. +It is recommended to configure the Kodi system to a static IP if the manual setup with IP is used. When using discovery, nymea +will re-detect kodi when its IP address changes. diff --git a/kodi/devicepluginkodi.cpp b/kodi/devicepluginkodi.cpp index 7a109a7d..c43bdc39 100644 --- a/kodi/devicepluginkodi.cpp +++ b/kodi/devicepluginkodi.cpp @@ -52,10 +52,15 @@ DevicePluginKodi::DevicePluginKodi() DevicePluginKodi::~DevicePluginKodi() { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + delete m_serviceBrowser; + delete m_httpServiceBrowser; } void DevicePluginKodi::init() { + m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_xbmc-jsonrpc._tcp"); + m_httpServiceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp"); + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginKodi::onPluginTimer); } @@ -64,9 +69,62 @@ void DevicePluginKodi::setupDevice(DeviceSetupInfo *info) { Device *device = info->device(); qCDebug(dcKodi) << "Setup Kodi device" << device->paramValue(kodiDeviceIpParamTypeId).toString(); + + QUuid kodiUuid = device->paramValue(kodiDeviceUuidParamTypeId).toUuid(); + + // The IP string is optional, we'll try to discover it in any case via zeroconf, however, if it's + // set in the params, we'll always fall back to that in case we can't find it on zeroconf. + + // The recommended way is to not store an IP in the settings as with DHCP lease times (or IPv6 privacy + // extension address randomization) an IP might expire eventually and it'll stop working. + + // So actually the params should *only* store the UUID, but we'll support manually entering IP, port and http port + // for setups that can't use ZeroConf for whatever reason. + QString ipString = device->paramValue(kodiDeviceIpParamTypeId).toString(); int port = device->paramValue(kodiDevicePortParamTypeId).toInt(); int httpPort = device->paramValue(kodiDeviceHttpPortParamTypeId).toInt(); + + if (!kodiUuid.isNull()) { + foreach (const ZeroConfServiceEntry &entry, m_serviceBrowser->serviceEntries()) { + QString uuid; + foreach (const QString &txt, entry.txt()) { + if (txt.startsWith("uuid")) { + uuid = txt.split("=").last(); + break; + } + } + + if (QUuid(uuid) == kodiUuid) { + ipString = entry.hostAddress().toString(); + port = entry.port(); + break; + } + } + foreach (const ZeroConfServiceEntry avahiEntry, m_httpServiceBrowser->serviceEntries()) { + QString uuid; + foreach (const QString &txt, avahiEntry.txt()) { + if (txt.startsWith("uuid")) { + uuid = txt.split("=").last(); + break; + } + } + if (QUuid(uuid) == kodiUuid) { + httpPort = avahiEntry.port(); + break; + } + } + } + + if (ipString.isEmpty()) { + // Ok, we could not find an ip on zeroconf... Let's try again in a second while setupInfo hasn't timed out. + qCDebug(dcKodi()) << "Device not found via ZeroConf... Waiting for a second for it to appear..."; + QTimer::singleShot(1000, info, [this, info](){ + setupDevice(info); + }); + return; + } + Kodi *kodi= new Kodi(QHostAddress(ipString), port, httpPort, this); connect(kodi, &Kodi::connectionStatusChanged, this, &DevicePluginKodi::onConnectionChanged); @@ -147,18 +205,11 @@ void DevicePluginKodi::deviceRemoved(Device *device) void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info) { - - ZeroConfServiceBrowser *serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_xbmc-jsonrpc._tcp"); - connect(info, &QObject::destroyed, serviceBrowser, &QObject::deleteLater); - - ZeroConfServiceBrowser *httpServiceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp"); - connect(info, &QObject::destroyed, httpServiceBrowser, &QObject::deleteLater); - - QTimer::singleShot(5000, info, [this, info, serviceBrowser, httpServiceBrowser](){ + QTimer::singleShot(5000, info, [this, info](){ QHash descriptors; - foreach (const ZeroConfServiceEntry avahiEntry, serviceBrowser->serviceEntries()) { + foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) { QString uuid; foreach (const QString &txt, avahiEntry.txt()) { @@ -176,9 +227,9 @@ void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info) qCDebug(dcKodi) << "Zeroconf entry:" << avahiEntry; DeviceDescriptor descriptor(kodiDeviceClassId, avahiEntry.name(), avahiEntry.hostName() + " (" + avahiEntry.hostAddress().toString() + ")"); ParamList params; - params << Param(kodiDeviceIpParamTypeId, avahiEntry.hostAddress().toString()); - params << Param(kodiDevicePortParamTypeId, avahiEntry.port()); params << Param(kodiDeviceUuidParamTypeId, uuid); +// params << Param(kodiDeviceIpParamTypeId, avahiEntry.hostAddress().toString()); + params << Param(kodiDevicePortParamTypeId, avahiEntry.port()); descriptor.setParams(params); Devices existing = myDevices().filterByParam(kodiDeviceUuidParamTypeId, uuid); @@ -189,8 +240,8 @@ void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info) descriptors.insert(uuid, descriptor); } - foreach (const ZeroConfServiceEntry avahiEntry, httpServiceBrowser->serviceEntries()) { -// qCDebug(dcKodi) << "Zeroconf http entry:" << avahiEntry; + foreach (const ZeroConfServiceEntry avahiEntry, m_httpServiceBrowser->serviceEntries()) { + qCDebug(dcKodi) << "Zeroconf http entry:" << avahiEntry; QString uuid; foreach (const QString &txt, avahiEntry.txt()) { if (txt.startsWith("uuid")) { @@ -209,7 +260,6 @@ void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info) descriptors[uuid] = descriptor; } - foreach (const DeviceDescriptor &d, descriptors.values()) { qCDebug(dcKodi()) << "Returning descritpor:" << d.params(); } diff --git a/kodi/devicepluginkodi.h b/kodi/devicepluginkodi.h index ad3fbb7b..8d36d317 100644 --- a/kodi/devicepluginkodi.h +++ b/kodi/devicepluginkodi.h @@ -31,6 +31,8 @@ #include #include +class ZeroConfServiceBrowser; + class DevicePluginKodi : public DevicePlugin { Q_OBJECT @@ -56,6 +58,8 @@ private: PluginTimer *m_pluginTimer; QHash m_kodis; QHash m_asyncSetups; + ZeroConfServiceBrowser *m_serviceBrowser = nullptr; + ZeroConfServiceBrowser *m_httpServiceBrowser = nullptr; QHash m_pendingActions; QHash m_pendingBrowserActions;