Kodi: Automatically redetect Kodi when its IP address changes

This commit is contained in:
Michael Zanetti 2019-11-17 00:53:24 +01:00
parent be230d9a3b
commit 8a074e3c37
3 changed files with 80 additions and 31 deletions

View File

@ -1,26 +1,21 @@
# Kodi # Kodi
This plugin allows you to controll the media center [Kodi](http://kodi.tv/). If you want to discover This plugin allows to integrate nymea with the [Kodi media center](http://kodi.tv/). The minimum requred version of
and control Kodi with nymea, you need to activate the remote access and the UPnP service. 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. 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
## Activate "Remote Control" will re-detect kodi when its IP address changes.
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)

View File

@ -52,10 +52,15 @@ DevicePluginKodi::DevicePluginKodi()
DevicePluginKodi::~DevicePluginKodi() DevicePluginKodi::~DevicePluginKodi()
{ {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
delete m_serviceBrowser;
delete m_httpServiceBrowser;
} }
void DevicePluginKodi::init() void DevicePluginKodi::init()
{ {
m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_xbmc-jsonrpc._tcp");
m_httpServiceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp");
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginKodi::onPluginTimer); connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginKodi::onPluginTimer);
} }
@ -64,9 +69,62 @@ void DevicePluginKodi::setupDevice(DeviceSetupInfo *info)
{ {
Device *device = info->device(); Device *device = info->device();
qCDebug(dcKodi) << "Setup Kodi device" << device->paramValue(kodiDeviceIpParamTypeId).toString(); 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(); QString ipString = device->paramValue(kodiDeviceIpParamTypeId).toString();
int port = device->paramValue(kodiDevicePortParamTypeId).toInt(); int port = device->paramValue(kodiDevicePortParamTypeId).toInt();
int httpPort = device->paramValue(kodiDeviceHttpPortParamTypeId).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); Kodi *kodi= new Kodi(QHostAddress(ipString), port, httpPort, this);
connect(kodi, &Kodi::connectionStatusChanged, this, &DevicePluginKodi::onConnectionChanged); connect(kodi, &Kodi::connectionStatusChanged, this, &DevicePluginKodi::onConnectionChanged);
@ -147,18 +205,11 @@ void DevicePluginKodi::deviceRemoved(Device *device)
void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info) void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info)
{ {
QTimer::singleShot(5000, info, [this, 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](){
QHash<QString, DeviceDescriptor> descriptors; QHash<QString, DeviceDescriptor> descriptors;
foreach (const ZeroConfServiceEntry avahiEntry, serviceBrowser->serviceEntries()) { foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) {
QString uuid; QString uuid;
foreach (const QString &txt, avahiEntry.txt()) { foreach (const QString &txt, avahiEntry.txt()) {
@ -176,9 +227,9 @@ void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info)
qCDebug(dcKodi) << "Zeroconf entry:" << avahiEntry; qCDebug(dcKodi) << "Zeroconf entry:" << avahiEntry;
DeviceDescriptor descriptor(kodiDeviceClassId, avahiEntry.name(), avahiEntry.hostName() + " (" + avahiEntry.hostAddress().toString() + ")"); DeviceDescriptor descriptor(kodiDeviceClassId, avahiEntry.name(), avahiEntry.hostName() + " (" + avahiEntry.hostAddress().toString() + ")");
ParamList params; ParamList params;
params << Param(kodiDeviceIpParamTypeId, avahiEntry.hostAddress().toString());
params << Param(kodiDevicePortParamTypeId, avahiEntry.port());
params << Param(kodiDeviceUuidParamTypeId, uuid); params << Param(kodiDeviceUuidParamTypeId, uuid);
// params << Param(kodiDeviceIpParamTypeId, avahiEntry.hostAddress().toString());
params << Param(kodiDevicePortParamTypeId, avahiEntry.port());
descriptor.setParams(params); descriptor.setParams(params);
Devices existing = myDevices().filterByParam(kodiDeviceUuidParamTypeId, uuid); Devices existing = myDevices().filterByParam(kodiDeviceUuidParamTypeId, uuid);
@ -189,8 +240,8 @@ void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info)
descriptors.insert(uuid, descriptor); descriptors.insert(uuid, descriptor);
} }
foreach (const ZeroConfServiceEntry avahiEntry, httpServiceBrowser->serviceEntries()) { foreach (const ZeroConfServiceEntry avahiEntry, m_httpServiceBrowser->serviceEntries()) {
// qCDebug(dcKodi) << "Zeroconf http entry:" << avahiEntry; qCDebug(dcKodi) << "Zeroconf http entry:" << avahiEntry;
QString uuid; QString uuid;
foreach (const QString &txt, avahiEntry.txt()) { foreach (const QString &txt, avahiEntry.txt()) {
if (txt.startsWith("uuid")) { if (txt.startsWith("uuid")) {
@ -209,7 +260,6 @@ void DevicePluginKodi::discoverDevices(DeviceDiscoveryInfo *info)
descriptors[uuid] = descriptor; descriptors[uuid] = descriptor;
} }
foreach (const DeviceDescriptor &d, descriptors.values()) { foreach (const DeviceDescriptor &d, descriptors.values()) {
qCDebug(dcKodi()) << "Returning descritpor:" << d.params(); qCDebug(dcKodi()) << "Returning descritpor:" << d.params();
} }

View File

@ -31,6 +31,8 @@
#include <QDebug> #include <QDebug>
#include <QTcpSocket> #include <QTcpSocket>
class ZeroConfServiceBrowser;
class DevicePluginKodi : public DevicePlugin class DevicePluginKodi : public DevicePlugin
{ {
Q_OBJECT Q_OBJECT
@ -56,6 +58,8 @@ private:
PluginTimer *m_pluginTimer; PluginTimer *m_pluginTimer;
QHash<Kodi*, Device*> m_kodis; QHash<Kodi*, Device*> m_kodis;
QHash<Kodi*, DeviceSetupInfo*> m_asyncSetups; QHash<Kodi*, DeviceSetupInfo*> m_asyncSetups;
ZeroConfServiceBrowser *m_serviceBrowser = nullptr;
ZeroConfServiceBrowser *m_httpServiceBrowser = nullptr;
QHash<int, DeviceActionInfo*> m_pendingActions; QHash<int, DeviceActionInfo*> m_pendingActions;
QHash<int, BrowserActionInfo*> m_pendingBrowserActions; QHash<int, BrowserActionInfo*> m_pendingBrowserActions;