diff --git a/piusv/__init__.py b/piusv/__init__.py new file mode 100755 index 000000000..4edc12583 --- /dev/null +++ b/piusv/__init__.py @@ -0,0 +1,240 @@ +#!/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.8 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 . +# +######################################################################### + +from lib.model.smartplugin import SmartPlugin +from lib.item import Items + +from .webif import WebInterface + +import smbus + +class piusv(SmartPlugin): + """ + Main class of the Plugin. Does all plugin specific stuff and provides the update functions for the items + """ + + PLUGIN_VERSION = '0.1.0' + + def __init__(self, sh): + """ + Initalizes the plugin. + + If you need the sh object at all, use the method self.get_sh() to get it. There should be almost no need for + a reference to the sh object anymore. + + """ + + # Call init code of parent class (SmartPlugin) + super().__init__() + + self._item_dict = {} + self._cyclic_update_active = False + self.alive = False + self.suspended = False + + # check if shNG is running on Raspberry Pi + try: + self.webif_pagelength = self.get_parameter_value('webif_pagelength') + self.poll_cycle = self.get_parameter_value('poll_cycle') + self.i2c_address = self.get_parameter_value('i2c_address') + except KeyError as e: + self.logger.critical("Plugin '{}': Inconsistent plugin (invalid metadata definition: {} not defined)".format(self.get_shortname(), e)) + self._init_complete = False + return + + self.init_webinterface(WebInterface) + return + +#################################################################################### +# Die Parameter der Pi USV+ byteweise auslesen + def get_parameter(self, index): + parameters = [0,0,0,0,0,0,0,0,0,0] + + try: + self.piusv_handle.write_byte(self.i2c_address, 0x02) + except (IOError): + self.logger.error("get_parameter: error writing to piusv") + return(0) + for i in range(10): + try: + parameters[i] = self.piusv_handle.read_byte(self.i2c_address) + except (IOError): + self.logger.error("get_parameter: error reading to piusv") + return(0) + value = 256*parameters[index] + parameters[index+1] + return value + +# Statusbyte auslesen + def get_status(self): + + try: + self.piusv_handle.write_byte(self.i2c_address, 0x00) + except (IOError): + self.logger.error("get_status: error writing to piusv") + return(0) + try: + status = self.piusv_handle.read_byte(self.i2c_address) + except (IOError): + self.logger.error("get_status: error reading to piusv") + return(0) + return status + +# Firmware auslesen + def get_firmware(self): + + version = '' + try: + self.piusv_handle.write_byte(self.i2c_address, 0x01) + except (IOError): + self.logger.error("get_get_firmware: error writing to piusv") + return(0) + for i in range (12): + try: + version = version + chr(self.piusv_handle.read_byte(self.i2c_address)) + except (IOError): + self.logger.error("get_firmware: error reading to piusv") + return(0) + return version +############################################################################### + def run(self): + """ + Run method for the plugin + """ + self.logger.debug("Run method called") + # Handle + self.piusv_handle = smbus.SMBus(1) + + # setup scheduler for device poll loop (disable the following line, if you don't need to poll the device. Rember to comment the self_cycle statement in __init__ as well) + self.scheduler_add('poll_device', self.poll_device, cycle=self.poll_cycle) + self.alive = True + + def stop(self): + """ + Stop method for the plugin + """ + self.logger.debug("Stop method called") + self.scheduler_remove('poll_device') + self.alive = False + + def parse_item(self, item): + """ + Default plugin parse_item method. Is called when the plugin is initialized. + The plugin can, corresponding to its attribute keywords, decide what to do with + the item in the future, like adding it to an internal array for future reference + :param item: The item to process. + :return: If the plugin needs to be informed of an items change you should return a call back function + like the function update_item down below. An example when this is needed is the knx plugin + where parse_item returns the update_item function when the attribute knx_send is found. + This means that when the items value is about to be updated, the call back function is called + with the item, caller, source and dest as arguments and in case of the knx plugin the value + can be sent to the knx with a knx write function within the knx plugin. + """ + if self.has_iattr(item.conf, 'piusv_func'): + self.logger.debug(f"parse item: {item}") + self._item_dict[item] = self.get_iattr_value(item.conf, 'piusv_func') + + elif self.has_iattr(item.conf, 'piusv_sys'): + return self.update_item + + def update_item(self, item, caller=None, source=None, dest=None): + """ + Item has been updated + + This method is called, if the value of an item has been updated by SmartHomeNG. + :param item: item to be updated towards the plugin + :param caller: if given it represents the callers name + :param source: if given it represents the source + :param dest: if given it represents the dest + """ + if self.alive and caller != self.get_shortname(): + # code to execute if the plugin is not stopped and only, if the item has not been changed by this plugin: + self.logger.info(f"Update item: {item.property.path}, item has been changed outside this plugin") + + if self.has_iattr(item.conf, 'piusv_sys'): + self.logger.debug(f"update_item was called with item {item.property.path} from caller {caller}, source {source} and dest {dest}") + if self.get_iattr_value(item.conf, 'piusv_sys') == 'update' and bool(item()): + self.logger.info(f"Update of all items of piusv Plugin requested. ") + self.poll_device() + item(False) + pass + + def poll_device(self): + """ + Polls for updates of the device + """ + # check if another cyclic cmd run is still active + if self._cyclic_update_active: + self.logger.warning('Triggered cyclic poll_device, but previous cyclic run is still active. Therefore request will be skipped.') + return + elif self.suspended: + self.logger.warning('Triggered cyclic poll_device, but Plugin in suspended. Therefore request will be skipped.') + return + else: + self.logger.info('Triggering cyclic poll_device') + + # set lock + self._cyclic_update_active = True + + for item in self._item_dict: + # self.logger.debug(f"poll_device: handle item {item.id()}") + value = eval(f"self.{self.get_iattr_value(item.conf, 'piusv_func')}()") + # self.logger.info(f"poll_device: {value=} for item {item.id()} will be set.") + item(value, self.get_shortname()) + + # release lock + self._cyclic_update_active = False + + pass + + def u_batt(self): + return self.get_parameter(0) + + def i_rasp(self): + return self.get_parameter(2) + + def u_rasp(self): + return self.get_parameter(4) + + def u_usb(self): + return self.get_parameter(6) + + def u_ext(self): + return self.get_parameter(8) + + def piusv_status(self): + return self.get_status() + + def piusv_firmware(self): + return self.get_firmware() + + @property + def item_list(self): + return list(self._item_dict.keys()) + + @property + def log_level(self): + return self.logger.getEffectiveLevel() diff --git a/piusv/locale.yaml b/piusv/locale.yaml new file mode 100755 index 000000000..a9e8c71ba --- /dev/null +++ b/piusv/locale.yaml @@ -0,0 +1,17 @@ +# translations for the web interface +plugin_translations: + 'Value': + de: 'Wert' + en: 'Value' + 'Attribute': + de: 'Attribut' + en: 'Attribute' + 'Last Change': + de: 'Letzte Änderung' + en: 'Last Change' + 'Last Update': + de: 'Letztes Update' + en: 'Last Update' + 'Type': + de: 'Typ' + en: 'Type' diff --git a/piusv/plugin.yaml b/piusv/plugin.yaml new file mode 100755 index 000000000..e7b379bbd --- /dev/null +++ b/piusv/plugin.yaml @@ -0,0 +1,205 @@ +# Metadata for the plugin +plugin: + # Global plugin attributes + type: interface # plugin type (gateway, interface, protocol, system, web) + description: + de: 'Plugin zum Auslesen Informationen aus der PIUSV+' + en: 'Plugin to readout information from PIUSV+' + maintainer: SergeoLacruz + tester: SergeoLacruz # Who tests this plugin? + state: develop # change to ready when done with development + keywords: usv +# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page +# support: https://knx-user-forum.de/forum/supportforen/smarthome-py + + version: 0.1.0 # Plugin version (must match the version specified in __init__.py) + sh_minversion: 1.8 # minimum shNG version to use this plugin +# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) +# py_minversion: 3.6 # minimum Python version to use for this plugin +# py_maxversion: # maximum Python version to use for this plugin (leave empty if latest) + multi_instance: false # plugin supports multi instance + restartable: unknown + classname: piusv # class containing the plugin + +parameters: + poll_cycle: + type: int + default: 120 + description: + de: 'Zyklus, in dem die Informationen abgefragt werden sollen.' + en: 'Poll cycle of information.' + i2c_address: + type: int + default: 0x18 + description: + de: 'I2C Adresse der PIUPS+' + en: 'I2C address of the PIUPS+' + + + +item_attributes: + piusv_func: + type: str + description: + de: 'Funktion des piusv Plugins' + en: 'Function of piusv Plugins' + valid_list: + - 'u_batt' + - 'i_rasp' + - 'u_rasp' + - 'u_usb' + - 'u_ext' + - 'piusv_status' + - 'piusv_firmware' + + piusv_sys: + type: str + description: + de: 'Attribut für globale Plugin Items zum Triggern eines Update' + en: 'Attribute for global plugin items to trigger value read update' + valid_list: + - update + valid_list_description: + de: + - 'Startet bei Triggerung ein Update der Daten' + en: + - 'Rereads rpi data' + +item_structs: + piusv_info: + name: Struct für Information über die piusv + + update_piusv_info: + type: bool + piusv_sys: update + + u_batt: + type: num + piusv_func: u_batt + visu_acl: ro + + i_rasp: + type: num + piusv_func: i_rasp + visu_acl: ro + + u_rasp: + type: num + piusv_func: u_rasp + visu_acl: ro + + u_ext: + type: num + piusv_func: u_ext + visu_acl: ro + + u_usb: + type: num + piusv_func: u_usb + visu_acl: ro + + piusv_status: + type: num + piusv_func: piusv_status + visu_acl: ro + + piusv_firmware: + type: str + piusv_func: piusv_firmware + visu_acl: ro + + piusv_info_db: + name: Struct für Information über piusv mit database + + update_piusv_info: + type: bool + piusv_sys: update + + u_batt: + type: num + piusv_func: u_batt + visu_acl: ro + + i_rasp: + type: num + piusv_func: i_rasp + visu_acl: ro + + u_rasp: + type: num + piusv_func: u_rasp + visu_acl: ro + + u_ext: + type: num + piusv_func: u_ext + visu_acl: ro + + u_usb: + type: num + piusv_func: u_usb + visu_acl: ro + + piusv_status: + type: num + piusv_func: piusv_status + visu_acl: ro + + piusv_firmware: + type: str + piusv_func: piusv_firmware + visu_acl: ro + +item_attribute_prefixes: NONE + +plugin_functions: + u_batt: + type: int + description: + de: 'Batteriespannung' + en: 'Battery Voltage' + + i_rasp: + type: int + description: + de: 'Stromaufnahme von Raspberry' + en: 'Current consumprion of raspberry' + + u_rasp: + type: bool + description: + de: 'Versorgungsspannung des Raspberry' + en: 'Supply voltage of raspberry' + + u_usb: + type: bool + description: + de: 'USB voltage' + en: 'Voltage on USB port' + + u_ext: + type: bool + description: + de: 'Versorgungsspannung der PIUSV+' + en: 'Supply voltage of PIUSV+' + + piusv_status: + type: bool + description: + de: 'Status der PIUSV+' + en: 'Status byte of PIUSV+' + + piusv_firmware: + type: str + description: + de: 'Firmware version der PIUSV+' + en: 'Firmware version byte of PIUSV+' + + suspend: + type: bool + description: + de: 'Pausiert das Plugins' + en: 'Suspends plugin' + +logic_parameters: NONE + # Definition of logic parameters defined by this plugin (enter 'logic_parameters: NONE', if section should be empty) diff --git a/piusv/sv_widgets/piusv.html b/piusv/sv_widgets/piusv.html new file mode 100644 index 000000000..200c12738 --- /dev/null +++ b/piusv/sv_widgets/piusv.html @@ -0,0 +1,54 @@ +/** +* ----------------------------------------------------------------------------- +* @package smartVISU +* @author Wolfram v. Hülsen +* @copyright 2018 - 2024 +* @license GPL [http://www.gnu.de] +* ----------------------------------------------------------------------------- +*/ + +/** +* Displays the status of the PiUSV +* +* @param {id=} unique id for this widget(optional) +* @param {item} main item path for piusv struct +* +*/ + +{% macro info(id, item) %} + {% import config_version_full >= "3.2.c" ? "@widgets/basic.html" : "basic.html" as basic %} + + + + + + + + + + + + + + +
Versorgungsspannung:{{basic.print('', item~'.u_ext', '%01,3f V','VAR1/1000' ) }}Batteriespannung:{{basic.print('', item~'.u_batt', '%01,3f V','VAR1/1000') }}
Spannung RPI:{{basic.print('', item~'.u_rasp', '%01,3f V','VAR1/1000' ) }}Strom RPI:{{basic.print('', item~'.i_rasp', 'mA' ) }}
+
+ {{ basic.symbol('', item~'.piusv_status', '', 'status_led', [0,1], 'VAR1 & 1', ['#C80101', '#01A001']) }}Externe Versorgung
+ {{ basic.symbol('', item~'.piusv_status', '', 'status_led', [0,2], 'VAR1 & 2', ['lightgrey', '#01A001']) }}Batteriebetrieb
+ {{ basic.symbol('', item~'.piusv_status', '', 'status_led', [0,4], 'VAR1 & 4', ['lightgray', '#C80101']) }}Batterie niedrig
+ {{ basic.symbol('', item~'.piusv_status', '', 'status_led', [0,8], 'VAR1 & 8', ['lightgray', '#01A001']) }}Batterie laden
+ {{ basic.symbol('', item~'.piusv_status', '', 'status_led', [0,16], 'VAR1 & 16', ['#C80101', '#01A001']) }}Batterie voll
+ +{% endmacro %} diff --git a/piusv/user_doc.rst b/piusv/user_doc.rst new file mode 100755 index 000000000..f7b50d61b --- /dev/null +++ b/piusv/user_doc.rst @@ -0,0 +1,31 @@ +.. index:: Plugins; piusv +.. index:: piusv + +======== +piusv +======== + +.. image:: webif/static/img/plugin_logo.png + :alt: plugin logo + :width: 300px + :height: 300px + :scale: 50 % + :align: left + +Unterstützte Geräte +=================== + +piusv+ + + +Konfiguration +============= + +Die Pluginparameter und die Informationen zur Item-spezifischen Konfiguration des Plugins sind +unter :doc:`/plugins_doc/config/piusv` beschrieben. + + +Web Interface +============= + +Das Plugin stellt ein WebIF zur Verfügung, in dem alle mit dem Plugin verknüpften Items gelistet sind. diff --git a/piusv/webif/__init__.py b/piusv/webif/__init__.py new file mode 100755 index 000000000..c89a8f6c3 --- /dev/null +++ b/piusv/webif/__init__.py @@ -0,0 +1,124 @@ +#!/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 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') + # Setting pagelength (max. number of table entries per page) for web interface + try: + pagelength = self.plugin.webif_pagelength + except Exception: + pagelength = 100 + # add values to be passed to the Jinja2 template eg: tmpl.render(p=self.plugin, interface=interface, ...) + return tmpl.render(p=self.plugin, + webif_pagelength=pagelength, + items=sorted(self.plugin.item_list, key=lambda k: str.lower(k['_path'])), + item_count=len(self.plugin.item_list), + plugin_shortname=self.plugin.get_shortname(), + plugin_version=self.plugin.get_version(), + plugin_info=self.plugin.get_info(), + maintenance=True if self.plugin.log_level <= 20 else False, + ) + + @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: + # get the new data + data = dict() + + data['items'] = {} + for item in self.plugin.item_list: + data['items'][item.id()] = {} + data['items'][item.id()]['value'] = item.property.value + data['items'][item.id()]['last_update'] = item.property.last_update.strftime('%d.%m.%Y %H:%M:%S') + data['items'][item.id()]['last_change'] = item.property.last_change.strftime('%d.%m.%Y %H:%M:%S') + data['plugin_suspended'] = self.plugin.suspended + data['maintenance'] = True if self.plugin.log_level <= 20 else False + try: + return json.dumps(data, default=str) + except Exception as e: + self.logger.error(f"get_data_html exception: {e}") + + @cherrypy.expose + def activate(self): + self.logger.debug(f"active called") + self.plugin.suspend(False) + + @cherrypy.expose + def suspend(self): + self.logger.debug(f"suspend called") + self.plugin.suspend(True) diff --git a/piusv/webif/static/img/plugin_logo.png b/piusv/webif/static/img/plugin_logo.png new file mode 100755 index 000000000..390b7e5af Binary files /dev/null and b/piusv/webif/static/img/plugin_logo.png differ diff --git a/piusv/webif/static/img/readme.txt b/piusv/webif/static/img/readme.txt new file mode 100755 index 000000000..1a7c55eef --- /dev/null +++ b/piusv/webif/static/img/readme.txt @@ -0,0 +1,6 @@ +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/piusv/webif/templates/index.html b/piusv/webif/templates/index.html new file mode 100755 index 000000000..4844a78ee --- /dev/null +++ b/piusv/webif/templates/index.html @@ -0,0 +1,94 @@ +{% extends "base_plugin.html" %} +{% set logo_frame = false %} +{% set update_interval = 30000 %} + + +{% block pluginstyles %} +{% endblock pluginstyles %} + + +{% block pluginscripts %} + + +{% endblock pluginscripts %} + +{% block headtable %} + + + + + + + + + + + + + +
ModelPIUSV+Interval{{ p.poll_cycle }}s
+{% endblock headtable %} + + + +{% set tabcount = 1 %} + +{% if item_count > 0 %} + {% set start_tab = 1 %} +{% endif %} + + + +{% set tab1title = "" ~ p.get_shortname() ~ " Items (" ~ item_count ~ ")" %} + + +{% block bodytab1 %} + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + {% endfor %} + +
{{ _('Item') }}{{ _('Attribute') }}{{_('Type')}}{{_('Value')}}{{_('Last Update')}}{{_('Last Change')}}
{{ item._path }}{{ p.get_iattr_value(item.conf, 'piusv_func') }}{{ item._type }}{{ item._value }}{{ item.property.last_update.strftime('%d.%m.%Y %H:%M:%S') }}{{ item.property.last_change.strftime('%d.%m.%Y %H:%M:%S') }}
+ +{% endblock bodytab1 %}