Kodi: Automatically redetect Kodi when its IP address changes
This commit is contained in:
parent
be230d9a3b
commit
8a074e3c37
@ -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".
|
||||||
|
|
||||||

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

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

|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user