From f3d5763242292b5394479751d7f292beaecf97e0 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 9 Jan 2020 20:32:57 +0100 Subject: [PATCH] New Plugin: UniFi network controller --- debian/control | 16 + debian/nymea-plugin-unifi.install.in | 1 + nymea-plugins.pro | 1 + unifi/README.md | 13 + unifi/devicepluginunifi.cpp | 363 ++++++++++++++++++ unifi/devicepluginunifi.h | 66 ++++ unifi/devicepluginunifi.json | 92 +++++ ...c00c7-9ea8-4aa6-8aec-831639e8fccc-en_US.ts | 124 ++++++ unifi/unifi.pro | 11 + 9 files changed, 687 insertions(+) create mode 100644 debian/nymea-plugin-unifi.install.in create mode 100644 unifi/README.md create mode 100644 unifi/devicepluginunifi.cpp create mode 100644 unifi/devicepluginunifi.h create mode 100644 unifi/devicepluginunifi.json create mode 100644 unifi/translations/88bc00c7-9ea8-4aa6-8aec-831639e8fccc-en_US.ts create mode 100644 unifi/unifi.pro diff --git a/debian/control b/debian/control index a37571ab..596620f0 100644 --- a/debian/control +++ b/debian/control @@ -595,6 +595,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}, @@ -874,6 +889,7 @@ Depends: nymea-plugin-anel, nymea-plugin-senic, nymea-plugin-sonos, 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 diff --git a/debian/nymea-plugin-unifi.install.in b/debian/nymea-plugin-unifi.install.in new file mode 100644 index 00000000..6caecb74 --- /dev/null +++ b/debian/nymea-plugin-unifi.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginunifi.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index ba6ef519..60a276e2 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -48,6 +48,7 @@ PLUGIN_DIRS = \ tcpcommander \ texasinstruments \ udpcommander \ + unifi \ unitec \ wakeonlan \ wemo \ diff --git a/unifi/README.md b/unifi/README.md new file mode 100644 index 00000000..3085daad --- /dev/null +++ b/unifi/README.md @@ -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. diff --git a/unifi/devicepluginunifi.cpp b/unifi/devicepluginunifi.cpp new file mode 100644 index 00000000..3dc6dde6 --- /dev/null +++ b/unifi/devicepluginunifi.cpp @@ -0,0 +1,363 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicepluginunifi.h" +#include "plugininfo.h" + +#include +#include +#include + +#include +#include +#include + +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); + } +} + diff --git a/unifi/devicepluginunifi.h b/unifi/devicepluginunifi.h new file mode 100644 index 00000000..9dc48b2d --- /dev/null +++ b/unifi/devicepluginunifi.h @@ -0,0 +1,66 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINUNIFI_H +#define DEVICEPLUGINUNIFI_H + +#include + +#include "devices/deviceplugin.h" + +#include + +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 m_pendingDiscoveries; + QHash m_pendingSiteDiscoveries; + + PluginTimer *m_loginTimer = nullptr; + PluginTimer *m_pollTimer = nullptr; +}; + +#endif // DEVICEPLUGINUNIFI_H diff --git a/unifi/devicepluginunifi.json b/unifi/devicepluginunifi.json new file mode 100644 index 00000000..0d941538 --- /dev/null +++ b/unifi/devicepluginunifi.json @@ -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 + } + ] + } + ] + } + ] +} diff --git a/unifi/translations/88bc00c7-9ea8-4aa6-8aec-831639e8fccc-en_US.ts b/unifi/translations/88bc00c7-9ea8-4aa6-8aec-831639e8fccc-en_US.ts new file mode 100644 index 00000000..d1ad9e74 --- /dev/null +++ b/unifi/translations/88bc00c7-9ea8-4aa6-8aec-831639e8fccc-en_US.ts @@ -0,0 +1,124 @@ + + + + + DevicePluginUnifi + + + Please configure a UniFi controller first. + + + + + + Fetching sites from controller failed. + + + + + Error communicating with the controller. + + + + + Please enter your login credentials for the UniFi controller. + + + + + unifi + + + Client connected/disconnected + The name of the EventType ({7c2420eb-31eb-43b8-b28c-0dba4a4a3910}) of DeviceClass client + + + + + + Client is connected + 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 + + + + + + Connected + 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 + + + + + Connected/disconnected + The name of the EventType ({2efc35f6-dc58-4cd2-98cc-7e0a1a4f4e01}) of DeviceClass controller + + + + + IP Address + The name of the ParamType (DeviceClass: controller, Type: device, ID: {9210506a-8c6a-41eb-8462-be93211fc9fe}) + + + + + + Last seen time + 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 + + + + + Last seen time changed + The name of the EventType ({8491c998-6100-4a1c-b0b7-6d44696aceba}) of DeviceClass client + + + + + Leave timeout + The name of the ParamType (DeviceClass: client, Type: settings, ID: {aa10389e-f4a4-44b8-ba1e-e641914425b6}) + + + + + MAC address + The name of the ParamType (DeviceClass: client, Type: device, ID: {a3e7ea90-3f92-4ccf-aec1-b9bc18bfa76f}) + + + + + Ubiquiti + The name of the vendor ({0ccc026c-4454-4948-8fcb-be2436d232dd}) + + + + + UniFi + The name of the plugin unifi ({88bc00c7-9ea8-4aa6-8aec-831639e8fccc}) + + + + + UniFi Controller + The name of the DeviceClass ({1da7534c-dd51-4cd2-ab56-48428892c436}) + + + + + UniFi Site + The name of the ParamType (DeviceClass: client, Type: device, ID: {32358acf-f5ea-4a7f-b4cb-325963118398}) + + + + + UniFi client + The name of the DeviceClass ({cf1a99ce-ad17-4cc7-8558-480daba20e72}) + + + + diff --git a/unifi/unifi.pro b/unifi/unifi.pro new file mode 100644 index 00000000..2bc916b0 --- /dev/null +++ b/unifi/unifi.pro @@ -0,0 +1,11 @@ +include(../plugins.pri) + +QT += network + +HEADERS += \ + devicepluginunifi.h \ + +SOURCES += \ + devicepluginunifi.cpp \ + +