1
+ """
2
+ This module provides a plugin manager class for loading plugins and reading/writing plugin data.
3
+ """
4
+ import fnmatch
5
+ import importlib
6
+ import json
7
+ import os
8
+ import sys
9
+
10
+ import myDevices .cloud .cayennemqtt as cayennemqtt
11
+ from myDevices .utils .config import Config
12
+ from myDevices .utils .logger import debug , error , exception , info
13
+
14
+
15
+ class PluginManager ():
16
+ """Loads plugins and reads/writes plugin data"""
17
+
18
+ def __init__ (self ):
19
+ """Initializes the plugin manager and loads the plugin list"""
20
+ self .plugin_folder = '/etc/myDevices/plugins'
21
+ self .plugins = {}
22
+ self .load_plugins ()
23
+
24
+ def load_plugin_from_file (self , filename ):
25
+ """Loads a plugin from a specified plugin config file and adds it to the plugin list"""
26
+ try :
27
+ info ('Loading plugin: {}' .format (filename ))
28
+ loaded = []
29
+ config = Config (filename )
30
+ plugin_name = os .path .splitext (os .path .basename (filename ))[0 ]
31
+ info ('Sections: {}' .format (config .sections ()))
32
+ for section in config .sections ():
33
+ enabled = config .get (section , 'enabled' , 'true' ).lower () == 'true'
34
+ if enabled :
35
+ plugin = {
36
+ 'channel' : config .get (section , 'channel' ),
37
+ 'name' : config .get (section , 'name' , section ),
38
+ 'module' : config .get (section , 'module' ),
39
+ 'class' : config .get (section , 'class' ),
40
+ 'init_args' : json .loads (config .get (section , 'init_args' , '{}' ))
41
+ }
42
+ folder = os .path .dirname (filename )
43
+ if folder not in sys .path :
44
+ sys .path .append (folder )
45
+ imported_module = importlib .import_module (plugin ['module' ])
46
+ device_class = getattr (imported_module , plugin ['class' ])
47
+ plugin ['instance' ] = device_class (** plugin ['init_args' ])
48
+ plugin ['read' ] = getattr (plugin ['instance' ], config .get (section , 'read' ))
49
+ try :
50
+ plugin ['write' ] = getattr (plugin ['instance' ], config .get (section , 'write' ))
51
+ except :
52
+ pass
53
+ self .plugins [plugin_name + ':' + plugin ['channel' ]] = plugin
54
+ loaded .append (section )
55
+ except Exception as e :
56
+ error (e )
57
+ info ('Loaded sections: {}' .format (loaded ))
58
+
59
+ def load_plugins (self ):
60
+ """Loads plugins from any plugin config files found in the plugin folder"""
61
+ for root , dirnames , filenames in os .walk (self .plugin_folder ):
62
+ for filename in fnmatch .filter (filenames , '*.plugin' ):
63
+ self .load_plugin_from_file (os .path .join (root , filename ))
64
+
65
+ def get_plugin_readings (self ):
66
+ """Return a list with current readings for all plugins"""
67
+ readings = []
68
+ for key , plugin in self .plugins .items ():
69
+ try :
70
+ value = plugin ['read' ]()
71
+ value_dict = self .convert_to_dict (value )
72
+ cayennemqtt .DataChannel .add (readings , cayennemqtt .DEV_SENSOR , key , name = plugin ['name' ], ** value_dict )
73
+ except KeyError as e :
74
+ debug ('Missing key {} in plugin \' {}\' ' .format (e , plugin ['name' ]))
75
+ except :
76
+ exception ('Error reading from plugin \' {}\' ' .format (plugin ['name' ]))
77
+ return readings
78
+
79
+ def convert_to_dict (self , value ):
80
+ """Convert a tuple value to a dict containing value, type and unit"""
81
+ value_dict = {}
82
+ try :
83
+ value_dict ['value' ] = value [0 ]
84
+ value_dict ['type' ] = value [1 ]
85
+ value_dict ['unit' ] = value [2 ]
86
+ except :
87
+ if not value_dict :
88
+ value_dict ['value' ] = value
89
+ if 'type' in value_dict and 'unit' not in value_dict :
90
+ if value_dict ['type' ] == 'digital_actuator' :
91
+ value_dict ['unit' ] = 'd'
92
+ elif value_dict ['type' ] == 'analog_actuator' :
93
+ value_dict ['unit' ] = 'null'
94
+ return value_dict
95
+
96
+ def is_plugin (self , plugin , channel ):
97
+ """Returns True if the specified plugin:channel is a valid plugin"""
98
+ return plugin + ':' + channel in self .plugins .keys ()
99
+
100
+ def write_value (self , plugin , channel , value ):
101
+ """Write a value to a plugin actuator.
102
+
103
+ Returns: True if value written, False if it was not"""
104
+ actuator = plugin + ':' + channel
105
+ info ('Write value {} to {}' .format (value , actuator ))
106
+ if actuator in self .plugins .keys ():
107
+ try :
108
+ self .plugins [actuator ]['write' ](value )
109
+ except :
110
+ return False
111
+ else :
112
+ return False
113
+ return True
0 commit comments