Merge PR #217: New Plugin: UniFi network controller

master
Jenkins nymea 2020-01-30 17:24:45 +01:00
commit 0526cbcea8
9 changed files with 687 additions and 0 deletions

16
debian/control vendored
View File

@ -640,6 +640,21 @@ Description: nymea.io plugin for UDP commander
This package will install the nymea.io plugin for udpcommander
Package: nymea-plugin-unifi
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-translations,
Description: nymea.io plugin for UniFi network controllers
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for UniFi network controllers
Package: nymea-plugin-unitec
Architecture: any
Depends: ${shlibs:Depends},
@ -937,6 +952,7 @@ Depends: nymea-plugin-anel,
nymea-plugin-sonos,
nymea-plugin-tado,
nymea-plugin-keba,
nymea-plugin-unifi,
Replaces: guh-plugins
Description: Plugins for nymea IoT server - the default plugin collection
The nymea daemon is a plugin based IoT (Internet of Things) server. The

1
debian/nymea-plugin-unifi.install.in vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginunifi.so

View File

@ -52,6 +52,7 @@ PLUGIN_DIRS = \
tplink \
tuya \
udpcommander \
unifi \
unitec \
wakeonlan \
wemo \

13
unifi/README.md Normal file
View File

@ -0,0 +1,13 @@
# UniFi
This plugin adds support to connect nymea to a UniFi network controller and monitor devices for presence.
## Setup
In order to monitor network devices via a UniFi controller, it is required to configure the UniFi controller.
The IP, as well as username and password for the UniFi controller must be provided.
Once the controller is added to the system, additional Wi-Fi devices may be added by starting a discovery of
UniFi clients. After a client is addded, it will appear as presence sensor in the system.
Client devices, by default have a one minute grace period before they are marked as offline. This value can
be changed in the device settings. A value of 0 will immediately mark a device as offline.

363
unifi/devicepluginunifi.cpp Normal file
View File

@ -0,0 +1,363 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "devicepluginunifi.h"
#include "plugininfo.h"
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonDocument>
#include <hardwaremanager.h>
#include <network/networkaccessmanager.h>
#include <plugintimer.h>
DevicePluginUnifi::DevicePluginUnifi(QObject *parent) : DevicePlugin(parent)
{
}
DevicePluginUnifi::~DevicePluginUnifi()
{
}
void DevicePluginUnifi::init()
{
}
void DevicePluginUnifi::discoverDevices(DeviceDiscoveryInfo *info)
{
Q_ASSERT_X(info->deviceClassId() == clientDeviceClassId, "discoverDevices", "Invalid device class in discovery");
Devices controllers = myDevices().filterByDeviceClassId(controllerDeviceClassId);
if (controllers.isEmpty()) {
info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Please configure a UniFi controller first."));
return;
}
connect(info, &DeviceDiscoveryInfo::aborted, this, [this, info](){
m_pendingDiscoveries.remove(info);
});
foreach (Device *controller, controllers) {
m_pendingDiscoveries[info].append(controller);
QNetworkRequest request = createRequest(controller, "/api/self/sites");
QNetworkReply *sitesReply = hardwareManager()->networkManager()->get(request);
connect(sitesReply, &QNetworkReply::finished, sitesReply, &QNetworkReply::deleteLater);
connect(sitesReply, &QNetworkReply::finished, info, [this, info, sitesReply, controller](){
if (sitesReply->error() != QNetworkReply::NoError) {
qCWarning(dcUnifi()) << "Error fetching sites";
m_pendingDiscoveries[info].removeAll(controller);
if (m_pendingDiscoveries[info].isEmpty()) {
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Fetching sites from controller failed."));
}
return;
}
QByteArray data = sitesReply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcUnifi()) << "Error parsing data" << data;
m_pendingDiscoveries[info].removeAll(controller);
if (m_pendingDiscoveries[info].isEmpty()) {
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error communicating with the controller."));
}
return;
}
if (jsonDoc.toVariant().toMap().value("meta").toMap().value("rc").toString() != "ok") {
qCWarning(dcUnifi()) << "Controller did not responde with OK" << qUtf8Printable(jsonDoc.toJson());
m_pendingDiscoveries[info].removeAll(controller);
if (m_pendingDiscoveries[info].isEmpty()) {
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Fetching sites from controller failed."));
}
return;
}
qCDebug(dcUnifi()) << "**** sites reply finished" << data;
foreach (const QVariant &siteVariant, jsonDoc.toVariant().toMap().value("data").toList()) {
qCDebug(dcUnifi()) << "Have site:" << siteVariant.toMap().value("_id").toString() << siteVariant.toMap().value("name").toString() << siteVariant.toMap().value("desc").toString();
QString site = siteVariant.toMap().value("_id").toString();
QString siteName = siteVariant.toMap().value("name").toString();
QString siteDescription = siteVariant.toMap().value("desc").toString();
QNetworkRequest request = createRequest(controller, QString("/api/s/%1/stat/sta/").arg(siteName));
qCDebug(dcUnifi()) << "Fetching clients for site" << site << siteName << request.url();
m_pendingSiteDiscoveries[controller].append(siteName);
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [this, info, reply, controller, siteName](){
m_pendingSiteDiscoveries[controller].removeAll(siteName);
if (m_pendingSiteDiscoveries[controller].isEmpty()) {
m_pendingDiscoveries[info].removeAll(controller);
}
bool hasError = false;
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcUnifi()) << "Error fetching clients from site" << reply->error() << reply->errorString();
hasError = true;
} else {
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcUnifi()) << "Error parsing json for clients reply:" << error.errorString() << data;
hasError = true;
} else {
QVariantMap response = jsonDoc.toVariant().toMap();
if (response.value("meta").toMap().value("rc").toString() != "ok") {
qCWarning(dcUnifi()) << "Error response from controller:" << qUtf8Printable(jsonDoc.toJson());
hasError = true;
} else {
QVariantList clients = response.value("data").toList();
foreach (const QVariant &clientVariant, clients) {
// qCDebug(dcUnifi()) << "client:" << qUtf8Printable(QJsonDocument::fromVariant(clientVariant).toJson());
QString name = clientVariant.toMap().value("name").toString();
if (name.isEmpty()) {
name = clientVariant.toMap().value("hostname").toString();
}
if (name.isEmpty()) {
name = clientVariant.toMap().value("oui").toString();
}
DeviceDescriptor d(clientDeviceClassId, name, clientVariant.toMap().value("mac").toString());
ParamList params;
params << Param(clientDeviceMacParamTypeId, clientVariant.toMap().value("mac").toString());
params << Param(clientDeviceSiteParamTypeId, siteName);
d.setParams(params);
Device *existingDevice = myDevices().findByParams(params);
if (existingDevice) {
d.setDeviceId(existingDevice->id());
}
d.setParentDeviceId(controller->id());
info->addDeviceDescriptor(d);
}
}
}
}
if (m_pendingDiscoveries[info].isEmpty()) {
info->finish(hasError ? Device::DeviceErrorHardwareFailure : Device::DeviceErrorNoError);
}
});
}
});
}
}
void DevicePluginUnifi::startPairing(DevicePairingInfo *info)
{
info->finish(Device::DeviceErrorNoError, QT_TR_NOOP("Please enter your login credentials for the UniFi controller."));
}
void DevicePluginUnifi::confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret)
{
QString host = info->params().paramValue(controllerDeviceIpAddressParamTypeId).toString();
QNetworkRequest request = createRequest(host, "/api/login");
QVariantMap login;
login.insert("username", username);
login.insert("password", secret);
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(login).toJson());
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [this, info, reply, username, secret](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcUnifi()) << "Network request error:" << reply->error() << reply->errorString();
info->finish(Device::DeviceErrorHardwareFailure);
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcUnifi()) << "Error parsing JSON response from controller:" << error.errorString() << data;
info->finish(Device::DeviceErrorHardwareFailure);
return;
}
pluginStorage()->beginGroup(info->deviceId().toString());
pluginStorage()->setValue("username", username);
pluginStorage()->setValue("password", secret);
pluginStorage()->endGroup();
info->finish(Device::DeviceErrorNoError);
});
}
void DevicePluginUnifi::setupDevice(DeviceSetupInfo *info)
{
if (info->device()->deviceClassId() == controllerDeviceClassId) {
QNetworkRequest request = createRequest(info->device(), "/api/login");
QVariantMap login;
pluginStorage()->beginGroup(info->device()->id().toString());
login.insert("username", pluginStorage()->value("username").toString());
login.insert("password", pluginStorage()->value("password").toString());
pluginStorage()->endGroup();
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(login).toJson());
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [this, info, reply](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcUnifi()) << "Network request error:" << reply->error() << reply->errorString();
info->finish(Device::DeviceErrorHardwareFailure);
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcUnifi()) << "Error parsing JSON response from controller:" << error.errorString() << data;
info->finish(Device::DeviceErrorHardwareFailure);
return;
}
info->device()->setStateValue(controllerConnectedStateTypeId, true);
info->finish(Device::DeviceErrorNoError);
});
}
if (info->device()->deviceClassId() == clientDeviceClassId) {
info->finish(Device::DeviceErrorNoError);
}
}
void DevicePluginUnifi::postSetupDevice(Device *device)
{
if (device->deviceClassId() == controllerDeviceClassId && !m_loginTimer) {
// Let's refresh the login every minute
m_loginTimer = hardwareManager()->pluginTimerManager()->registerTimer();
connect(m_loginTimer, &PluginTimer::timeout, this, [this](){
foreach (Device *controller, myDevices().filterByDeviceClassId(controllerDeviceClassId)) {
QNetworkRequest request = createRequest(controller, "/api/login");
QVariantMap login;
pluginStorage()->beginGroup(controller->id().toString());
login.insert("username", pluginStorage()->value("username"));
login.insert("password", pluginStorage()->value("password"));
pluginStorage()->endGroup();
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(login).toJson());
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
}
});
}
if (device->deviceClassId() == clientDeviceClassId && !m_pollTimer) {
m_pollTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
connect(m_pollTimer, &PluginTimer::timeout, this, [this](){
foreach (Device *client, myDevices().filterByDeviceClassId(clientDeviceClassId)) {
Device *controller = myDevices().findById(client->parentId());
QString mac = client->paramValue(clientDeviceMacParamTypeId).toString();
QString site = client->paramValue(clientDeviceSiteParamTypeId).toString();
QNetworkRequest request = createRequest(controller, QString("/api/s/%1/stat/sta/%2").arg(site).arg(mac));
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, client, [this, client, reply](){
if (reply->error() != QNetworkReply::NoError) {
// If the device is not present we'll get an InvalidOperationError, silence that as it's expected but print other failures
if (reply->error() != QNetworkReply::ProtocolInvalidOperationError) {
qCDebug(dcUnifi()) << "Error fetching device state from controller" << reply->error() << reply->errorString();
}
markOffline(client);
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcUnifi()) << "Error parsing json from controller:" << error.error << error.errorString() << "\n" << data;
markOffline(client);
return;
}
// qCDebug(dcUnifi()) << "Client is present reply" << qUtf8Printable(jsonDoc.toJson());
QVariantList clientEntries = jsonDoc.toVariant().toMap().value("data").toList();
if (clientEntries.count() != 1) {
qCWarning(dcUnifi()) << "Client data not found in controller reply";
markOffline(client);
return;
}
QVariantMap clientData = clientEntries.first().toMap();
client->setStateValue(clientLastSeenTimeStateTypeId, clientData.value("last_seen").toInt());
client->setStateValue(clientIsPresentStateTypeId, true);
});
}
});
}
}
void DevicePluginUnifi::deviceRemoved(Device *device)
{
Q_UNUSED(device)
if (myDevices().filterByDeviceClassId(controllerDeviceClassId).isEmpty() && m_loginTimer) {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_loginTimer);
m_loginTimer = nullptr;
}
if (myDevices().filterByDeviceClassId(clientDeviceClassId).isEmpty() && m_pollTimer) {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pollTimer);
m_pollTimer = nullptr;
}
}
QNetworkRequest DevicePluginUnifi::createRequest(const QString &address, const QString &path)
{
QUrl url;
url.setScheme("https");
url.setHost(address);
url.setPort(8443);
url.setPath(path);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setPeerVerifyMode(QSslSocket::VerifyNone);
request.setSslConfiguration(config);
return request;
}
QNetworkRequest DevicePluginUnifi::createRequest(Device *device, const QString &path)
{
QString ipAddress = device->paramValue(controllerDeviceIpAddressParamTypeId).toString();
return createRequest(ipAddress, path);
}
void DevicePluginUnifi::markOffline(Device *device)
{
uint gracePeriod = device->setting(clientSettingsGracePeriodParamTypeId).toUInt();
QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(device->stateValue(clientLastSeenTimeStateTypeId).toInt() * 1000);
if (lastSeenTime.addSecs(gracePeriod * 60) < QDateTime::currentDateTime()) {
device->setStateValue(clientIsPresentStateTypeId, false);
}
}

66
unifi/devicepluginunifi.h Normal file
View File

@ -0,0 +1,66 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINUNIFI_H
#define DEVICEPLUGINUNIFI_H
#include <QObject>
#include "devices/deviceplugin.h"
#include <QNetworkRequest>
class PluginTimer;
class DevicePluginUnifi : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginunifi.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginUnifi(QObject *parent = nullptr);
~DevicePluginUnifi() override;
void init() override;
void discoverDevices(DeviceDiscoveryInfo *info) override;
void startPairing(DevicePairingInfo *info) override;
void confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret) override;
void setupDevice(DeviceSetupInfo *info) override;
void postSetupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
// void executeAction(DeviceActionInfo *info) override;
private:
QNetworkRequest createRequest(const QString &address, const QString &path);
QNetworkRequest createRequest(Device *device, const QString &path);
void markOffline(Device *device);
private:
QHash<DeviceDiscoveryInfo*, Devices> m_pendingDiscoveries;
QHash<Device*, QStringList> m_pendingSiteDiscoveries;
PluginTimer *m_loginTimer = nullptr;
PluginTimer *m_pollTimer = nullptr;
};
#endif // DEVICEPLUGINUNIFI_H

View File

@ -0,0 +1,92 @@
{
"displayName": "UniFi",
"name": "unifi",
"id": "88bc00c7-9ea8-4aa6-8aec-831639e8fccc",
"vendors": [
{
"id": "0ccc026c-4454-4948-8fcb-be2436d232dd",
"name": "ubiquiti",
"displayName": "Ubiquiti",
"deviceClasses": [
{
"id": "1da7534c-dd51-4cd2-ab56-48428892c436",
"name": "controller",
"displayName": "UniFi Controller",
"createMethods": ["user"],
"setupMethod": "userandpassword",
"interfaces": ["gateway"],
"paramTypes": [
{
"id": "9210506a-8c6a-41eb-8462-be93211fc9fe",
"name": "ipAddress",
"displayName": "IP Address",
"type": "QString"
}
],
"stateTypes": [
{
"id": "2efc35f6-dc58-4cd2-98cc-7e0a1a4f4e01",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected/disconnected",
"type": "bool",
"cached": false,
"defaultValue": false
}
]
},
{
"id": "cf1a99ce-ad17-4cc7-8558-480daba20e72",
"name": "client",
"displayName": "UniFi client",
"createMethods": ["discovery"],
"interfaces": ["presencesensor"],
"paramTypes": [
{
"id": "a3e7ea90-3f92-4ccf-aec1-b9bc18bfa76f",
"name": "mac",
"displayName": "MAC address",
"type": "QString"
},
{
"id": "32358acf-f5ea-4a7f-b4cb-325963118398",
"name": "site",
"displayName": "UniFi Site",
"type": "QString"
}
],
"settingsTypes": [
{
"id": "aa10389e-f4a4-44b8-ba1e-e641914425b6",
"name": "gracePeriod",
"displayName": "Leave timeout",
"type": "uint",
"defaultValue": "1",
"unit": "Minutes"
}
],
"stateTypes": [
{
"id": "7c2420eb-31eb-43b8-b28c-0dba4a4a3910",
"name": "isPresent",
"displayName": "Client is connected",
"displayNameEvent": "Client connected/disconnected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "8491c998-6100-4a1c-b0b7-6d44696aceba",
"name": "lastSeenTime",
"displayName": "Last seen time",
"displayNameEvent": "Last seen time changed",
"type": "int",
"unit": "UnixTime",
"defaultValue": 0
}
]
}
]
}
]
}

View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>DevicePluginUnifi</name>
<message>
<location filename="../devicepluginunifi.cpp" line="54"/>
<source>Please configure a UniFi controller first.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../devicepluginunifi.cpp" line="72"/>
<location filename="../devicepluginunifi.cpp" line="92"/>
<source>Fetching sites from controller failed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../devicepluginunifi.cpp" line="83"/>
<source>Error communicating with the controller.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../devicepluginunifi.cpp" line="179"/>
<source>Please enter your login credentials for the UniFi controller.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>unifi</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="37"/>
<source>Client connected/disconnected</source>
<extracomment>The name of the EventType ({7c2420eb-31eb-43b8-b28c-0dba4a4a3910}) of DeviceClass client</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="40"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="43"/>
<source>Client is connected</source>
<extracomment>The name of the ParamType (DeviceClass: client, EventType: isPresent, ID: {7c2420eb-31eb-43b8-b28c-0dba4a4a3910})
----------
The name of the StateType ({7c2420eb-31eb-43b8-b28c-0dba4a4a3910}) of DeviceClass client</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="46"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="49"/>
<source>Connected</source>
<extracomment>The name of the ParamType (DeviceClass: controller, EventType: connected, ID: {2efc35f6-dc58-4cd2-98cc-7e0a1a4f4e01})
----------
The name of the StateType ({2efc35f6-dc58-4cd2-98cc-7e0a1a4f4e01}) of DeviceClass controller</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="52"/>
<source>Connected/disconnected</source>
<extracomment>The name of the EventType ({2efc35f6-dc58-4cd2-98cc-7e0a1a4f4e01}) of DeviceClass controller</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="55"/>
<source>IP Address</source>
<extracomment>The name of the ParamType (DeviceClass: controller, Type: device, ID: {9210506a-8c6a-41eb-8462-be93211fc9fe})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="58"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="61"/>
<source>Last seen time</source>
<extracomment>The name of the ParamType (DeviceClass: client, EventType: lastSeenTime, ID: {8491c998-6100-4a1c-b0b7-6d44696aceba})
----------
The name of the StateType ({8491c998-6100-4a1c-b0b7-6d44696aceba}) of DeviceClass client</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="64"/>
<source>Last seen time changed</source>
<extracomment>The name of the EventType ({8491c998-6100-4a1c-b0b7-6d44696aceba}) of DeviceClass client</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="67"/>
<source>Leave timeout</source>
<extracomment>The name of the ParamType (DeviceClass: client, Type: settings, ID: {aa10389e-f4a4-44b8-ba1e-e641914425b6})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="70"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (DeviceClass: client, Type: device, ID: {a3e7ea90-3f92-4ccf-aec1-b9bc18bfa76f})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="73"/>
<source>Ubiquiti</source>
<extracomment>The name of the vendor ({0ccc026c-4454-4948-8fcb-be2436d232dd})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="76"/>
<source>UniFi</source>
<extracomment>The name of the plugin unifi ({88bc00c7-9ea8-4aa6-8aec-831639e8fccc})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="79"/>
<source>UniFi Controller</source>
<extracomment>The name of the DeviceClass ({1da7534c-dd51-4cd2-ab56-48428892c436})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="82"/>
<source>UniFi Site</source>
<extracomment>The name of the ParamType (DeviceClass: client, Type: device, ID: {32358acf-f5ea-4a7f-b4cb-325963118398})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/unifi/plugininfo.h" line="85"/>
<source>UniFi client</source>
<extracomment>The name of the DeviceClass ({cf1a99ce-ad17-4cc7-8558-480daba20e72})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

11
unifi/unifi.pro Normal file
View File

@ -0,0 +1,11 @@
include(../plugins.pri)
QT += network
HEADERS += \
devicepluginunifi.h \
SOURCES += \
devicepluginunifi.cpp \