diff --git a/unifi/README.md b/unifi/README.md deleted file mode 100755 index f2f73b650..000000000 --- a/unifi/README.md +++ /dev/null @@ -1,218 +0,0 @@ -# Unifi - -#### Version 1.6.1 - -Plugin to read some data from UniFi Controllers and to control it - -## Change history - -### Changes Since version 1.6.0 - -- Fixed logging issues -- Extended Web-Interface - -## Todo - -* Add additional data as Itemtypes from the UniFi API, based on real-world demands. - -## Requirements - -This plugin requires a permanently available UniFi Controller or a UniFi cloud-key. - -### Needed software - -There is no additional requirement for software that is not bundled. Plugin was tested with Python 3.5 and the api was tested with Python 3.5 and 3.7 - -### Supported Hardware - -As this plugin only communicates with the UniFi Controller basically all UniFi managed devices can be supported. - -Tested with: -* UniFi Controller 5.10.23 in Docker Container on Synology -* UniFi UAP AC Lite -* UniFi UAP AC Mesh -* UniFi Switch US-8-60W - -## Configuration - -### plugin.yaml -Please refer to the documentation generated from plugin.yaml metadata. - -Example: -``` -unifi: - plugin_name: unifi - unifi_user: ubnt # User Name - unifi_password: ubnt # Password - unifi_controller_url: https://192.168.1.12:8443 # URL of YOUR controller / cloud-key - poll_cycle_time: 60 # Cycle time for data retrieval in seconds - -``` - -### items.yaml - -Please refer to the documentation generated from plugin.yaml metadata. - -Example: -``` -uni_items: - type: foo - - switch: - unifi_switch_mac: af:fe:af:fe:00:00 - - ip: - type: str - unifi_type: device_ip - - sw_name: - type: str - unifi_type: device_name - - access_points: - unifi_switch_port_profile_on: All - unifi_switch_port_profile_off: Disabled - - ap_ac_lite1: - unifi_ap_mac: af:fe:af:fe:00:01 - unifi_switch_port_no: 5 - - ip: - type: str - unifi_type: device_ip - - ap_name: - type: str - unifi_type: device_name - - ap_enabled: - type: bool - unifi_type: ap_enabled - - port_enabled: - type: bool - unifi_type: switch_port_enabled - - jensIphoneHier: - type: bool - mac: a4:e9:00:00:af:fe - unifi_type: client_present_at_ap - - ap_ac_lite2: - unifi_ap_mac: af:fe:af:fe:00:02 - unifi_switch_port_no: 6 - - ip: - type: str - unifi_type: device_ip - - ap_name: - type: str - unifi_type: device_name - - ap_enabled: - type: bool - unifi_type: ap_enabled - - port_enabled: - type: bool - unifi_type: switch_port_enabled - - jensIphoneHier: - type: bool - mac: a4:e9:00:00:af:fe - unifi_type: client_present_at_ap - - - ap_ac_mesh: - unifi_ap_mac: af:fe:af:fe:af:fe - unifi_switch_port_no: 8 - - ap_enabled: - type: bool - unifi_type: ap_enabled - - port_enabled: - type: bool - unifi_type: switch_port_enabled - unifi_switch_port_profile_on: WLAN AP - unifi_switch_port_profile_off: Fully Off - - ip: - type: str - unifi_type: device_ip - - ap_name: - type: str - unifi_type: device_name - - myPhoneHere: - type: bool - mac: a4:e9:75:00:af:fe - unifi_type: client_present_at_ap - - myPhonePresent: - type: bool - mac: a4:e9:75:00:af:fe - unifi_type: client_present - - ip: - type: str - unifi_type: client_ip - - internetRadioDevicePresent: - type: bool - unifi_type: client_present - mac: 00:00:00:00:af:fe - - ip: - type: str - unifi_type: client_ip - -``` - - - -### logic.yaml -Please refer to the documentation generated from plugin.yaml metadata. - - -## Methods -Please refer to the documentation generated from plugin.yaml metadata. - - -## Examples - -If you have extensive examples, you could describe them here. - - -## Web Interfaces - -For building a web interface for a plugin, we deliver the following 3rd party components with the HTTP module: - - * JQuery 3.4.1: - * JS: <script src="/gstatic/js/jquery-3.4.1.min.js"></script> - * Bootstrap : - * CSS: <link rel="stylesheet" href="/gstatic/bootstrap/css/bootstrap.min.css" type="text/css"/> - * JS: <script src="/gstatic/bootstrap/js/bootstrap.min.js"></script> - * Bootstrap Tree View: - * CSS: <link rel="stylesheet" href="/gstatic/bootstrap-treeview/bootstrap-treeview.css" type="text/css"/> - * JS: <script src="/gstatic/bootstrap-treeview/bootstrap-treeview.min.js"></script> - * Bootstrap Datepicker v1.8.0: - * CSS: <link rel="stylesheet" href="/gstatic/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css" type="text/css"/> - * JS: - * <script src="/gstatic/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script> - * <script src="/gstatic/bootstrap-datepicker/dist/locales/bootstrap-datepicker.de.min.js"></script> - * popper.js: - * JS: <script src="/gstatic/popper.js/popper.min.js"></script> - * CodeMirror 5.46.0: - * CSS: <link rel="stylesheet" href="/gstatic/codemirror/lib/codemirror.css"/> - * JS: <script src="/gstatic/codemirror/lib/codemirror.js"></script> - * Font Awesome 5.8.1: - * CSS: <link rel="stylesheet" href="/gstatic/fontawesome/css/all.css" type="text/css"/> - - For addons, etc. that are delivered with the components, see /modules/http/webif/gstatic folder! - - If you are interested in new "global" components, contact us. Otherwise feel free to use them in your plugin, as long as - the Open Source license is ok. - diff --git a/unifi/__init__.py b/unifi/__init__.py index ec86fadb4..c47220d43 100755 --- a/unifi/__init__.py +++ b/unifi/__init__.py @@ -5,8 +5,7 @@ ######################################################################### # This file is part of SmartHomeNG. # -# Sample plugin for new plugins to run with SmartHomeNG version 1.4 and -# upwards. +# Plugin to read and control some features of UniFi Controllers # # SmartHomeNG is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,6 +28,7 @@ import cherrypy from lib.module import Modules from lib.model.smartplugin import * +from .webif import WebInterface from lib.utils import Utils from plugins.unifi.ubiquiti.unifi import API as UniFiAPI from plugins.unifi.ubiquiti.unifi import DataException as UniFiDataException @@ -289,13 +289,13 @@ class UniFiControllerClient(SmartPlugin): the update functions for the items """ - PLUGIN_VERSION = '1.6.2' + PLUGIN_VERSION = '1.6.3' def __init__(self, sh, *args, **kwargs): """ Initalizes the plugin. The parameters describe for this method are pulled from the entry in plugin.yaml. """ - + super().__init__() from bin.smarthome import VERSION if '.'.join(VERSION.split('.', 2)[:2]) <= '1.5': self.logger = logging.getLogger(__name__) @@ -319,7 +319,7 @@ def __init__(self, sh, *args, **kwargs): self._cycle = self.get_parameter_value(UniFiConst.PARAMETER_CYCLE_TIME) self._logging = True - self.init_webinterface() + self.init_webinterface(WebInterface) return @@ -657,85 +657,3 @@ def poll_device(self): except ConnectionError as ex: self._pollfailed += 1 self.logger.error("Poll failed: {} for {} time(s) in a row.".format(repr(ex), self._pollfailed)) - - def init_webinterface(self): - """" - Initialize the web interface for this plugin - - This method is only needed if the plugin is implementing a web interface - """ - try: - # try/except to handle running in a core version that does not support modules - self.mod_http = Modules.get_instance().get_module('http') - except: - self.mod_http = None - if self.mod_http == None: - self.logger.error("Not initializing the web interface") - return False - - import sys - if not "SmartPluginWebIf" in list(sys.modules['lib.model.smartplugin'].__dict__): - self.logger.warning("Web interface needs SmartHomeNG v1.5 and up. Not initializing the web interface") - return False - - # set application configuration for cherrypy - webif_dir = self.path_join(self.get_plugin_dir(), 'webif') - config = { - '/': { - 'tools.staticdir.root': webif_dir, - }, - '/static': { - 'tools.staticdir.on': True, - 'tools.staticdir.dir': 'static' - } - } - - # Register the web interface as a cherrypy app - self.mod_http.register_webif(WebInterface(webif_dir, self), - self.get_shortname(), - config, - self.get_classname(), self.get_instance_name(), - description='') - - return True - - -# ------------------------------------------ -# Webinterface of the plugin -# ------------------------------------------ - - -class WebInterface(SmartPluginWebIf): - - def __init__(self, webif_dir, plugin): - """ - Initialization of instance of class WebInterface - - :param webif_dir: directory where the webinterface of the plugin resides - :param plugin: instance of the plugin - :type webif_dir: str - :type plugin: object - """ - self.logger = logging.getLogger(__name__) - self.webif_dir = webif_dir - self.plugin = plugin - self.tplenv = self.init_template_environment() - - @cherrypy.expose - def index(self, reload=None): - """ - Build index.html for cherrypy - - Render the template and return the html file to be delivered to the browser - - :return: contents of the template after beeing rendered - """ - - tabcount = 2 - - tmpl = self.tplenv.get_template('index.html') - return tmpl.render(plugin_shortname=self.plugin.get_shortname(), - plugin_version=self.plugin.get_version(), - plugin_info=self.plugin.get_info(), - tabcount=tabcount, - p=self.plugin) diff --git a/unifi/assets/webif_unifi.png b/unifi/assets/webif_unifi.png new file mode 100644 index 000000000..1ac93ab0d Binary files /dev/null and b/unifi/assets/webif_unifi.png differ diff --git a/unifi/assets/webif_unifi_generator.png b/unifi/assets/webif_unifi_generator.png new file mode 100644 index 000000000..eb36f144f Binary files /dev/null and b/unifi/assets/webif_unifi_generator.png differ diff --git a/unifi/locale.yaml b/unifi/locale.yaml index 9cd18593f..fe8586a94 100755 --- a/unifi/locale.yaml +++ b/unifi/locale.yaml @@ -7,5 +7,5 @@ plugin_translations: 'Letztes Update': {'de': '=', 'en': 'Last update'} 'Letzter Change': {'de': '=', 'en': 'Last change'} 'PollCycle': {'de': 'Abfragezyklus (s)', 'en': 'Poll cycle (s)'} - - + 'Probleme': {'de': '=', 'en': 'Issues'} + 'Keine': {'de': '=', 'en': 'None'} diff --git a/unifi/plugin.yaml b/unifi/plugin.yaml index 343654488..285c0eacd 100755 --- a/unifi/plugin.yaml +++ b/unifi/plugin.yaml @@ -12,7 +12,7 @@ plugin: # documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1278068-unifi-controller-api-wlan - version: 1.6.2 # Plugin version + version: 1.6.3 # Plugin version sh_minversion: 1.9.0 # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) multi_instance: False # plugin supports multi instance diff --git a/unifi/user_doc.rst b/unifi/user_doc.rst new file mode 100755 index 000000000..e75ac40b2 --- /dev/null +++ b/unifi/user_doc.rst @@ -0,0 +1,75 @@ +.. index:: Plugins; unifi +.. index:: unifi + +===== +unifi +===== + +.. image:: webif/static/img/plugin_logo.png + :alt: plugin logo + :width: 400px + :height: 400px + :scale: 50 % + :align: left + +Anforderungen +============= + +Dieses Plugin benötigt einen ständig verfügbaren UniFi Controller oder einen UniFi +Cloud-Schlüssel. + + +Unterstützte Hardware +===================== + +Da dieses Plugin nur mit dem UniFi Controller kommuniziert, können grundsätzlich alle +UniFi verwaltete Geräte unterstützt werden. + +Getestet mit: + +* UniFi Controller 5.10.23 im Docker Container auf Synology +* UniFi UAP AC Lite, Mesh, Longrange, Pro +* UniFi Switch US-8-60W + +Konfiguration +============= + +.. important:: + + Detaillierte Informationen zur Konfiguration des Plugins sind unter :doc:`/plugins_doc/config/unifi` zu finden. + + +.. code-block:: yaml + + # etc/plugin.yaml + unifi: + plugin_name: unifi + unifi_user: ubnt # User Name + unifi_password: ubnt # Password + unifi_controller_url: https://192.168.1.12:8443 # URL of YOUR controller / cloud-key + poll_cycle_time: 60 # Cycle time for data retrieval in seconds + +Das Plugin sollte aktiviert und SHNG neu gestartet werden. Im Web Interface ist +der Item-Generator zu öffnen. Der Text sollte 1:1 in ein items/Unifi.yaml File +kopiert werden. Switch-Ports sind mit ``unifi_switch_port_profile_on`` und +``unifi_switch_port_profile_off`` zu ergänzen/aktualisieren. + +.. image:: assets/webif_unifi_generator.png + :height: 1524px + :width: 3312px + :scale: 25% + :alt: Web Interface Item-Generator + :align: center + +Web Interface +============= + +Neben dem Item-Generator bietet das Web Interface Informationen zu sämtlichen +konfigurierten Unifi Geräten: Item, Typ, Unifi-Typ, aktueller Wert, letzte Aktualisierung und letzte Änderung. + +.. image:: assets/webif_unifi.png + :height: 1524px + :width: 3312px + :scale: 25% + :alt: Web Interface + :align: center diff --git a/unifi/webif/__init__.py b/unifi/webif/__init__.py new file mode 100755 index 000000000..41708ab26 --- /dev/null +++ b/unifi/webif/__init__.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2020- +######################################################################### +# This file is part of SmartHomeNG. +# https://www.smarthomeNG.de +# https://knx-user-forum.de/forum/supportforen/smarthome-py +# +# Sample plugin for new plugins to run with SmartHomeNG version 1.5 and +# upwards. +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +# +######################################################################### + +import datetime +import time +import os +import json + +from lib.item import Items +from lib.model.smartplugin import SmartPluginWebIf + + +# ------------------------------------------ +# Webinterface of the plugin +# ------------------------------------------ + +import cherrypy +import csv +from jinja2 import Environment, FileSystemLoader + + +class WebInterface(SmartPluginWebIf): + + def __init__(self, webif_dir, plugin): + """ + Initialization of instance of class WebInterface + + :param webif_dir: directory where the webinterface of the plugin resides + :param plugin: instance of the plugin + :type webif_dir: str + :type plugin: object + """ + self.logger = plugin.logger + self.webif_dir = webif_dir + self.plugin = plugin + self.items = Items.get_instance() + + self.tplenv = self.init_template_environment() + + + @cherrypy.expose + def index(self, reload=None): + """ + Build index.html for cherrypy + + Render the template and return the html file to be delivered to the browser + + :return: contents of the template after beeing rendered + """ + + tmpl = self.tplenv.get_template('index.html') + pagelength = self.plugin.get_parameter_value('webif_pagelength') + return tmpl.render(plugin_shortname=self.plugin.get_shortname(), + webif_pagelength=pagelength, + plugin_version=self.plugin.get_version(), + plugin_info=self.plugin.get_info(), + p=self.plugin) + + + @cherrypy.expose + def get_data_html(self, dataSet=None): + """ + Return data to update the webpage + + For the standard update mechanism of the web interface, the dataSet to return the data for is None + + :param dataSet: Dataset for which the data should be returned (standard: None) + :return: dict with the data needed to update the web page. + """ + if dataSet is None: + data = {'items': {}, 'requests': 0} + for item in self.plugin._model.get_items(): + data['items'].update({item.id(): {'value': str(item.property.value), 'last_update': item.property.last_update.strftime('%d.%m.%Y %H:%M:%S'), 'last_change': item.property.last_change.strftime('%d.%m.%Y %H:%M:%S')}}) + data['requests'] = self.plugin._model.get_total_number_of_requests_to_controller() + try: + return json.dumps(data) + except Exception as e: + self.logger.error(f"get_data_html exception: {e}") + return {} diff --git a/unifi/webif/static/img/plugin_logo.png b/unifi/webif/static/img/plugin_logo.png new file mode 100644 index 000000000..dac492c82 Binary files /dev/null and b/unifi/webif/static/img/plugin_logo.png differ diff --git a/unifi/webif/static/img/readme.txt b/unifi/webif/static/img/readme.txt deleted file mode 100755 index 1a7c55eef..000000000 --- a/unifi/webif/static/img/readme.txt +++ /dev/null @@ -1,6 +0,0 @@ -This directory is for storing images that are used by the web interface. - -If you want to have your own logo on the top of the web interface, store it here and name it plugin_logo.. - -Extension can be png, svg or jpg - diff --git a/unifi/webif/templates/index.html b/unifi/webif/templates/index.html index 134020a98..a3c914d80 100755 --- a/unifi/webif/templates/index.html +++ b/unifi/webif/templates/index.html @@ -1,13 +1,108 @@ {% extends "base_plugin.html" %} - +{% set update_interval = 5000 %} {% set logo_frame = false %} - -{% block headtable %} +{% block pluginstyles %} + +{% endblock pluginstyles %} +{% block pluginscripts %} + + +{% endblock pluginscripts %} +{% block headtable %} - + @@ -33,21 +128,6 @@
{{ _('NumFromStart') }}{{ p._model.get_total_number_of_requests_to_controller() }}{{ p._model.get_total_number_of_requests_to_controller() }} Controller URL {{ p._model.get_controller_url() }}
{% endblock headtable %} - - -{% block buttons %} -{% if 1==2 %} -
- -
-{% endif %} -{% endblock %} - - {% set tabcount = 2 %} @@ -64,104 +144,53 @@ --> {% set tab1title = "Items (" ~ p._model.get_item_count() ~ ")" %} {% block bodytab1 %} - -
-
- - - - - - - - - - - - - - {% for item in p._model.get_items() %} - - - - - - - - - - - - - - - - - {% endfor %} - -
 {{ _('Pfad') }}{{ _('Typ') }}unifi_type{{ _('Item Wert') }}{{ _('Letztes Update') }}{{ _('Letzter Change') }}
- {% set warn_level = p._model.get_item_issues(item.id()).get_worst_level() %} - {% if warn_level == 1 %} - - - {% elif warn_level == 2 %} - - - {% elif warn_level == 3 %} - - - {% elif warn_level == 4 %} - - - {% endif %} -
{{ item.id() }}
{{ item.type() }}{{ item.conf['unifi_type'] }}{{ item() }}{{ item.last_update().strftime('%d.%m.%Y %H:%M:%S') }}{{ item.last_change().strftime('%d.%m.%Y %H:%M:%S') }}
 
  - {% for issue in p._model.get_item_issues(item.id()).get_issues() %} - {% if issue[0:2] == "1:" %} -   {{ issue[2:400] }} - {% elif issue[0:2] == "2:" %} - {{ issue[2:400] }} - {% elif issue[0:2] == "3:" %} - {{ issue[2:400] }} - {% elif issue[0:2] == "4:" %} - {{ issue[2:400] }} - {% else %} - {{ issue }} - {% endif %} -
- {% endfor %} -
-
-
- - + + + + + + + + + + + + + + {% for item in p._model.get_items() %} + + + + + + + + + {% endfor %} + +
{{ _('Pfad') }}{{ _('Typ') }}unifi_type{{ _('Item Wert') }}{{ _('Letztes Update') }}{{ _('Letzter Change') }}{{ _('Probleme') }}
{{ item.id() }}
{{ item.type() }}{{ item.conf['unifi_type'] }}{{ item() }}{{ item.last_update().strftime('%d.%m.%Y %H:%M:%S') }}{{ item.last_change().strftime('%d.%m.%Y %H:%M:%S') }} + {% if p._model.get_item_issues(item.id()).get_issues() | length == 0 %} + {{ _('Keine')}} + {% else %} + {% for issue in p._model.get_item_issues(item.id()).get_issues() %} + {% if p._model.get_item_issues(item.id()).get_issues() | length > 1 and loop.index == 1 %} +
+ {% endif %} + {% if issue[0:2] == "1:" %} + {{ issue[2:400] }} + {% elif issue[0:2] == "2:" %} + {{ issue[2:400] }} + {% elif issue[0:2] == "3:" %} + {{ issue[2:400] }} + {% elif issue[0:2] == "4:" %} + {{ issue[2:400] }} + {% else %} + {{ issue }} + {% endif %} +
+ {% endfor %} + {% endif %} +
{% endblock bodytab1 %} @@ -173,4 +202,4 @@ {{ p._model.get_item_hierarchy() }} -{% endblock bodytab2 %} \ No newline at end of file +{% endblock bodytab2 %}