Kodi: Automatically redetect Kodi when its IP address changes

master
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
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.

View File

@ -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<QString, DeviceDescriptor> 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();
}

View File

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