Skip to content

Commit f220dca

Browse files
committed
Add plugin manager.
1 parent d4639ae commit f220dca

File tree

3 files changed

+124
-0
lines changed

3 files changed

+124
-0
lines changed

myDevices/plugins/__init__.py

Whitespace-only changes.

myDevices/plugins/manager.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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

myDevices/sensors/sensors.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from myDevices.utils.types import M_JSON
2020
from myDevices.system.systeminfo import SystemInfo
2121
from myDevices.cloud import cayennemqtt
22+
from myDevices.plugins.manager import PluginManager
2223

2324
REFRESH_FREQUENCY = 5 #seconds
2425
# SENSOR_INFO_SLEEP = 0.05
@@ -44,6 +45,7 @@ def __init__(self):
4445
if results:
4546
for row in results:
4647
self.disabledSensors[row[0]] = 1
48+
self.pluginManager = PluginManager()
4749
self.StartMonitoring()
4850

4951
def SetDataChanged(self, onDataChanged=None, onSystemInfo=None):
@@ -73,6 +75,7 @@ def Monitor(self):
7375
self.currentSystemState = []
7476
self.MonitorSystemInformation()
7577
self.MonitorSensors()
78+
self.MonitorPlugins()
7679
self.MonitorBus()
7780
if self.currentSystemState != self.systemData:
7881
changedSystemData = self.currentSystemState
@@ -91,6 +94,12 @@ def MonitorSensors(self):
9194
return
9295
self.currentSystemState += self.SensorsInfo()
9396

97+
def MonitorPlugins(self):
98+
"""Check plugin states for changes"""
99+
if self.exiting.is_set():
100+
return
101+
self.currentSystemState += self.pluginManager.get_plugin_readings()
102+
94103
def MonitorBus(self):
95104
"""Check bus states for changes"""
96105
if self.exiting.is_set():
@@ -353,6 +362,8 @@ def SensorCommand(self, command, sensorId, channel, value):
353362
result = False
354363
info('SensorCommand: {}, sensor {}, channel {}, value {}'.format(command, sensorId, channel, value))
355364
try:
365+
if self.pluginManager.is_plugin(sensorId, channel):
366+
return self.pluginManager.write_value(sensorId, channel, value)
356367
commands = {'integer': {'function': 'write', 'value_type': int},
357368
'value': {'function': 'write', 'value_type': int},
358369
'function': {'function': 'setFunctionString', 'value_type': str},

0 commit comments

Comments
 (0)