22# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
33#########################################################################
44# Copyright 2012-2013 Marcus Popp marcus@popp.mx
5- # Copyright 2020 Bernd Meiners Bernd.Meiners@mail.de
5+ # Copyright 2020,2025 Bernd Meiners Bernd.Meiners@mail.de
6+ # Copyright 2025 ghciv6
67#########################################################################
78# This file is part of SmartHomeNG.
89# https://www.smarthomeNG.de
910# https://knx-user-forum.de/forum/supportforen/smarthome-py
1011#
11- # Sample plugin for new plugins to run with SmartHomeNG version 1.4 and
12- # upwards.
12+ # rrd plugin to run with SmartHomeNG version 1.10 and upwards.
1313#
1414# SmartHomeNG is free software: you can redistribute it and/or modify
1515# it under the terms of the GNU General Public License as published by
2626#
2727#########################################################################
2828
29- from lib .module import Modules
30- from lib .model .smartplugin import *
29+ from lib .model .smartplugin import SmartPlugin
3130from lib .item import Items
3231
32+ from .webif import WebInterface
33+
3334import datetime
3435import functools
3536import os
@@ -50,7 +51,7 @@ class RRD(SmartPlugin):
5051 Documentation can be found at `<https://pythonhosted.org/rrdtool/>`_
5152 """
5253
53- PLUGIN_VERSION = '1.6.2 '
54+ PLUGIN_VERSION = '1.7.0 '
5455
5556 def __init__ (self , sh ):
5657 """
@@ -60,10 +61,6 @@ def __init__(self, sh):
6061 # Call init code of parent class (SmartPlugin)
6162 super ().__init__ ()
6263
63- from bin .smarthome import VERSION
64- if '.' .join (VERSION .split ('.' , 2 )[:2 ]) <= '1.5' :
65- self .logger = logging .getLogger (__name__ )
66-
6764 # get the parameters for the plugin (as defined in metadata plugin.yaml):
6865 rrd_dir = self .get_parameter_value ('rrd_dir' )
6966 if not rrd_dir :
@@ -75,17 +72,21 @@ def __init__(self, sh):
7572 try :
7673 os .makedirs (self ._rrd_dir )
7774 except :
78- self .logger .error ("Unable to create directory '{}'" . format ( self ._rrd_dir ) )
75+ self .logger .error (f "Unable to create directory '{ self ._rrd_dir } '" )
7976
8077 self ._rrds = {}
8178 self .step = self .get_parameter_value ('step' )
8279
8380 # Initialization code goes here
8481 if not REQUIRED_PACKAGE_IMPORTED :
8582 self ._init_complete = False
86- self .logger .error ("{ }: Unable to import Python package 'rrdtool'". format ( self . get_fullname ()) )
83+ self .logger .error (f" { self . get_fullname () } : Unable to import Python package 'rrdtool'" )
8784 return
8885
86+ self .init_webinterface (WebInterface )
87+ # if plugin should not start without web interface
88+ # if not self.init_webinterface():
89+ # self._init_complete = False
8990 return
9091
9192 def run (self ):
@@ -122,7 +123,7 @@ def parse_item(self, item):
122123
123124 # set database filename
124125 dbname = ''
125- self .logger .debug ("parse item: {}" . format ( item ) )
126+ self .logger .debug (f "parse item: { item } " )
126127 if self .has_iattr (item .conf ,'rrd_ds_name' ):
127128 dbname = self .get_iattr_value (item .conf , 'rrd_ds_name' )
128129 if not dbname :
@@ -149,7 +150,7 @@ def parse_item(self, item):
149150 no_series = self .get_iattr_value (item .conf ,'rrd_no_series' )
150151
151152 if no_series :
152- self .logger .warning ("Attribute rrd_no_series is set to True, no data series will be provided for item {}" . format ( item .property .path ) )
153+ self .logger .warning ("Attribute rrd_no_series is set to True, no data series will be provided for item {item.property.path}" )
153154 else :
154155 item .series = functools .partial (self ._series , item = item .property .path )
155156 item .db = functools .partial (self ._single , item = item .property .path )
@@ -162,6 +163,11 @@ def parse_item(self, item):
162163 item .set (last , 'RRDtool' )
163164
164165 def _update_cycle (self ):
166+ """
167+ this is called by scheduler to update all items at the same time
168+ it is currently fixed by parameter "step" from plugin.yaml
169+ thus any other step you would set for an item would not be served
170+ """
165171 for itempath in self ._rrds :
166172 rrd = self ._rrds [itempath ]
167173 if rrd ['type' ] == 'GAUGE' :
@@ -174,7 +180,7 @@ def _update_cycle(self):
174180 value
175181 )
176182 except Exception as e :
177- self .logger .warning ("RRD: error updating {}: {}" . format ( itempath , e ) )
183+ self .logger .warning (f "RRD: error updating { itempath } : { e } " )
178184 continue
179185
180186
@@ -193,50 +199,50 @@ def _series(self, func, start='1d', end='now', count=100, ratio=1, update=False,
193199 if item in self ._rrds :
194200 rrd = self ._rrds [item ]
195201 else :
196- self .logger .warning ("RRDtool: not enabled for {}" . format ( item ) )
202+ self .logger .warning (f "RRDtool: not enabled for { item } " )
197203 return
198- query = ["{}" . format ( rrd ['rrdb' ]) ]
204+ query = [f" { rrd ['rrdb' ]} " ]
199205 # prepare consolidation function
200- if func == 'avg' :
206+ if func == 'avg' or func == 'raw' :
201207 query .append ('AVERAGE' )
202208 elif func == 'max' :
203209 if not rrd ['max' ]:
204- self .logger .warning ("RRDtool: unsupported consolidation function {} for {}" . format ( func , item ) )
210+ self .logger .warning (f "RRDtool: unsupported consolidation function { func } for { item } " )
205211 return
206212 query .append ('MAX' )
207213 elif func == 'min' :
208214 if not rrd ['min' ]:
209- self .logger .warning ("RRDtool: unsupported consolidation function {} for {}" . format ( func , item ) )
215+ self .logger .warning (f "RRDtool: unsupported consolidation function { func } for { item } " )
210216 return
211217 query .append ('MIN' )
212218 elif func == 'raw' :
213219 query .append ('AVERAGE' )
214- self .logger .warning ("RRDtool: unsupported consolidation function {} for {}. Using average instead" . format ( func , item ) )
220+ self .logger .warning (f "RRDtool: unsupported consolidation function { func } for { item } . Using average instead" )
215221 else :
216- self .logger .warning ("RRDtool: unsupported consolidation function {} for {}" . format ( func , item ) )
222+ self .logger .warning (f "RRDtool: unsupported consolidation function { func } for { item } " )
217223 return
218224 # set time frame for query
219225 if start .isdigit ():
220- query .extend (['--start' , "{}" . format ( start ) ])
226+ query .extend (['--start' , f" { start } " ])
221227 else :
222- query .extend (['--start' , "now-{}" . format ( start ) ])
228+ query .extend (['--start' , f "now-{ start } " ])
223229 if end != 'now' :
224230 if end .isdigit ():
225- query .extend (['--end' , "{}" . format ( end ) ])
231+ query .extend (['--end' , f" { end } " ])
226232 else :
227- query .extend (['--end' , "now-{}" . format ( end ) ])
233+ query .extend (['--end' , f "now-{ end } " ])
228234 if step is not None :
229235 query .extend (['--resolution' , step ])
230236 # run query
231237 try :
232238 meta , name , data = rrdtool .fetch (* query )
233239 except Exception as e :
234- self .logger .warning ("error reading {0 } data: {1}" . format ( item , e ) )
240+ self .logger .warning (f "error reading { item } data: { e } " )
235241 return None
236242
237243 # postprocess values
238244 if sid is None :
239- sid = "{ }|{}|{}|{}|{}" . format ( item , func , start , end , count )
245+ sid = f" { item } |{ func } |{ start } |{ end } |{ count } "
240246 reply = {'cmd' : 'series' , 'series' : None , 'sid' : sid }
241247 istart , iend , istep = meta
242248 mstart = istart * 1000
@@ -247,29 +253,32 @@ def _series(self, func, start='1d', end='now', count=100, ratio=1, update=False,
247253 for i , v in enumerate (data ):
248254 if v [0 ] is not None :
249255 tuples .append ((mstart + i * mstep , v [0 ]))
256+ iend = int ( ( mstart + i * mstep ) / 1000 ) # added by ghciv6
250257 reply ['series' ] = sorted (tuples )
251- reply ['params' ] = {'update' : True , 'item' : item , 'func' : func , 'start' : str (iend ), 'end' : str (iend + istep ), 'step' : str (istep ), 'sid' : sid }
258+ #reply['params'] = {'update': True, 'item': item, 'func': func, 'start': str(iend), 'end': str(iend + istep), 'step': str(istep), 'sid': sid} # old
259+ reply ['params' ] = {'update' : True , 'item' : item , 'func' : func , 'start' : str (iend + istep ), 'end' : str (iend + 2 * istep ), 'step' : str (istep ), 'sid' : sid } # changed by ghciv6
252260 reply ['update' ] = self .get_sh ().now () + datetime .timedelta (seconds = istep )
253- self .logger .warning ("Returning series for {} from {} to {} with {} values" .format (sid , iend , iend + istep , len (tuples ) ))
261+ #self.logger.warning(f"Returning series for {sid} from {iend} to {iend+istep} with {len(tuples)} values") # old
262+ self .logger .info (f"Returning series for { sid } from { istart } to { iend } with { len (tuples )} values" ) # changed by ghciv6
254263 return reply
255264
256265 def _single (self , func , start = '1d' , end = 'now' , item = None ):
257266 """
258267 Reads a single value from rrd.
259268
260- :param func: String with consolidating function 'avg', 'min', 'max', 'last' to use
269+ :param func: String with consolidating function 'avg' (or 'raw') , 'min', 'max', 'last' to use
261270 :param start: String containing a start time
262271 :param end: String containing a start time
263272 """
264273 if item in self ._rrds :
265274 rrd = self ._rrds [item ]
266275 else :
267- self .logger .warning ("RRDtool: not enabled for {}" . format ( item ) )
276+ self .logger .warning (f "RRDtool: not enabled for { item } " )
268277 return
269278
270279 # prepare consolidation function
271- query = ["{}" . format ( rrd ['rrdb' ]) ]
272- if func == 'avg' :
280+ query = [f" { rrd ['rrdb' ]} " ]
281+ if func == 'avg' or func == 'raw' :
273282 query .append ('AVERAGE' )
274283 elif func == 'max' :
275284 if rrd ['max' ]:
@@ -284,35 +293,35 @@ def _single(self, func, start='1d', end='now', item=None):
284293 elif func == 'last' :
285294 query .append ('AVERAGE' )
286295 elif func == 'raw' :
287- self .logger .warning ("RRDtool: unsupported consolidation function {} for {}. Using average instead" . format ( func , item ) )
296+ self .logger .warning (f "RRDtool: unsupported consolidation function { func } for { item } . Using average instead" )
288297 query .append ('AVERAGE' )
289298 else :
290- self .logger .warning ("RRDtool: unsupported consolidation function {} for {}" . format ( func , item ) )
299+ self .logger .warning (f "RRDtool: unsupported consolidation function { func } for { item } " )
291300 return
292301
293302 # set time frame for query
294303 if start .isdigit ():
295- query .extend (['--start' , "{}" . format ( start ) ])
304+ query .extend (['--start' , f" { start } " ])
296305 else :
297- query .extend (['--start' , "now-{}" . format ( start ) ])
306+ query .extend (['--start' , f "now-{ start } " ])
298307 if end != 'now' :
299308 if end .isdigit ():
300- query .extend (['--end' , "{}" . format ( end ) ])
309+ query .extend (['--end' , f" { end } " ])
301310 else :
302- query .extend (['--end' , "now-{}" . format ( end ) ])
311+ query .extend (['--end' , f "now-{ end } " ])
303312
304313 # execute query
305314 try :
306315 meta , name , data = rrdtool .fetch (* query )
307316 except Exception as e :
308- self .logger .warning ("error reading {0 } data: {1}" . format ( item , e ) )
317+ self .logger .warning (f "error reading { item } data: { e } " )
309318 return None
310319
311320 # unpack returned values
312321 values = [v [0 ] for v in data if v [0 ] is not None ]
313322
314323 # postprocess for consolidation
315- if func == 'avg' :
324+ if func == 'avg' or func == 'raw' :
316325 if len (values ) > 0 :
317326 return sum (values ) / len (values )
318327 elif func == 'min' :
@@ -325,7 +334,7 @@ def _single(self, func, start='1d', end='now', item=None):
325334 if len (values ) > 0 :
326335 return values [- 1 ]
327336 elif func == 'raw' :
328- self .logger .warning ("Unsupported consolidation function {0 } for {1 }. Using last instead" . format ( func , item ) )
337+ self .logger .warning (f "Unsupported consolidation function { func } for { item } . Using last instead" )
329338 if len (values ) > 0 :
330339 return values [- 1 ]
331340
@@ -337,24 +346,24 @@ def _create(self, rrd):
337346 args = [rrd ['rrdb' ]]
338347 item_id = rrd ['id' ].rpartition ('.' )[2 ][:19 ]
339348
340- args .append ("DS:{}:{}:{}:U:U" . format ( item_id , rrd ['type' ], str (2 * rrd ['step' ])) )
349+ args .append (f "DS:{ item_id } :{ rrd ['type' ]} : { str (2 * rrd ['step' ])} :U:U" )
341350 if rrd ['min' ]:
342- args .append (' RRA:MIN:0.5:{}:1825' . format ( int (86400 / rrd ['step' ])) ) # 24h/5y
351+ args .append (f" RRA:MIN:0.5:{ int (86400 / rrd ['step' ])} :1825" ) # 24h/5y
343352 if rrd ['max' ]:
344- args .append (' RRA:MAX:0.5:{}:1825' . format ( int (86400 / rrd ['step' ])) ) # 24h/5y
353+ args .append (f" RRA:MAX:0.5:{ int (86400 / rrd ['step' ])} :1825" ) # 24h/5y
345354 args .extend (['--step' , str (rrd ['step' ])])
346355
347356 if rrd ['type' ] == 'GAUGE' :
348- args .append (' RRA:AVERAGE:0.5:1:{}' . format ( int (86400 / rrd ['step' ]) * 7 + 8 )) # 7 days
349- args .append (' RRA:AVERAGE:0.5:{}:1536' . format ( int (1800 / rrd ['step' ]))) # 0.5h/32 days
350- args .append (' RRA:AVERAGE:0.5:{}:1600' . format ( int (21600 / rrd ['step' ]))) # 6h/400 days
351- args .append (' RRA:AVERAGE:0.5:{}:1826' . format ( int (86400 / rrd ['step' ]))) # 24h/5y
352- args .append (' RRA:AVERAGE:0.5:{}:1300' . format ( int (604800 / rrd ['step' ]))) # 7d/25y
357+ args .append (f" RRA:AVERAGE:0.5:1:{ int (86400 / rrd ['step' ]) * 7 + 8 } " ) # 7 days
358+ args .append (f" RRA:AVERAGE:0.5:{ int (1800 / rrd ['step' ])} :1536" ) # 0.5h/32 days
359+ args .append (f" RRA:AVERAGE:0.5:{ int (21600 / rrd ['step' ])} :1600" ) # 6h/400 days
360+ args .append (f" RRA:AVERAGE:0.5:{ int (86400 / rrd ['step' ])} :1826" ) # 24h/5y
361+ args .append (f" RRA:AVERAGE:0.5:{ int (604800 / rrd ['step' ])} :1300" ) # 7d/25y
353362 elif rrd ['type' ] == 'COUNTER' :
354- args .append (' RRA:AVERAGE:0.5:{}:1826' . format ( int (86400 / rrd ['step' ]))) # 24h/5y
355- args .append (' RRA:AVERAGE:0.5:{}:1300' . format ( int (604800 / rrd ['step' ]))) # 7d/25y
363+ args .append (f" RRA:AVERAGE:0.5:{ int (86400 / rrd ['step' ])} :1826" ) # 24h/5y
364+ args .append (f" RRA:AVERAGE:0.5:{ int (604800 / rrd ['step' ])} :1300" ) # 7d/25y
356365 try :
357366 rrdtool .create (* args )
358- self .logger .debug ("Creating rrd ({0}) for {1}." . format ( rrd ['rrdb' ], rrd ['item' ]) )
367+ self .logger .debug (f "Creating rrd ({ rrd ['rrdb' ]} ) for { rrd ['item' ]} ." )
359368 except Exception as e :
360- self .logger .warning ("Error creating rrd ({0}) for {1}: {2}" . format ( rrd ['rrdb' ], rrd ['item' ], e ) )
369+ self .logger .warning (f "Error creating rrd ({ rrd ['rrdb' ]} ) for { rrd ['item' ]} : { e } " )
0 commit comments