Skip to content

Commit 371541a

Browse files
committed
v0.8.6 Added Trigger Groups
- Triggers can now be defined in a group - Trigger groups require all triggers active to execute actions - Updated triggers to work with groups without changing single trigger functions
1 parent 86d3911 commit 371541a

File tree

8 files changed

+182
-67
lines changed

8 files changed

+182
-67
lines changed

mudpi.config.example

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,35 @@
9595
],
9696
"triggers": [
9797
{
98-
"type": "control",
99-
"source": "button_1",
100-
"key": "button_1_trigger",
101-
"name": "Override Button Pressed",
102-
"actions": ["turn_on_lights_1", "save_to_file"],
103-
"frequency":"once",
104-
"thresholds": [
98+
"group":"Trigger Group 1",
99+
"actions": ["turn_on_lights_1"],
100+
"triggers":[
105101
{
106-
"comparison":"eq",
107-
"value":true
102+
"type": "control",
103+
"source": "button_1",
104+
"key": "button_1_trigger",
105+
"name": "Override Button Pressed",
106+
"frequency":"once",
107+
"thresholds": [
108+
{
109+
"comparison":"eq",
110+
"value":true
111+
}
112+
]
113+
},
114+
{
115+
"type": "sensor",
116+
"source": "weather",
117+
"nested_source":"temperature",
118+
"key": "temp_trigger",
119+
"name": "Temp too hot",
120+
"frequency":"once",
121+
"thresholds": [
122+
{
123+
"comparison":"gte",
124+
"value":70
125+
}
126+
]
108127
}
109128
]
110129
},

mudpi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
print('_________________________________________________')
6464
print('')
6565
print('Eric Davisson @theDavisson')
66-
print('Version: ', CONFIGS.get('version', '0.8.5'))
66+
print('Version: ', CONFIGS.get('version', '0.8.6'))
6767
print('\033[0;0m')
6868

6969
if CONFIGS['debug'] is True:

triggers/control_trigger.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
class ControlTrigger(Trigger):
1010

11-
def __init__(self, main_thread_running, system_ready, name='ControlTrigger',key=None, source=None, thresholds=None, channel="controls", trigger_active=None, frequency='once', actions=[]):
12-
super().__init__(main_thread_running, system_ready, name=name, key=key, source=source, thresholds=thresholds, trigger_active=trigger_active, frequency=frequency, actions=actions, trigger_interval=0.5)
11+
def __init__(self, main_thread_running, system_ready, name='ControlTrigger',key=None, source=None, thresholds=None, channel="controls", trigger_active=None, frequency='once', actions=[], group=None):
12+
super().__init__(main_thread_running, system_ready, name=name, key=key, source=source, thresholds=thresholds, trigger_active=trigger_active, frequency=frequency, actions=actions, trigger_interval=0.5, group=group)
1313
self.channel = channel.replace(" ", "_").lower() if channel is not None else "controls"
1414
return
1515

@@ -23,8 +23,9 @@ def init_trigger(self):
2323
def check(self):
2424
while self.main_thread_running.is_set():
2525
if self.system_ready.is_set():
26+
super().check()
2627
self.pubsub.get_message()
27-
self.trigger_active.clear()
28+
# self.trigger_active.clear()
2829
time.sleep(self.trigger_interval)
2930
else:
3031
time.sleep(2)

triggers/sensor_trigger.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
class SensorTrigger(Trigger):
1010

11-
def __init__(self, main_thread_running, system_ready, name='SensorTrigger',key=None, source=None, nested_source=None, thresholds=None, channel="sensors", trigger_active=None, frequency='once', actions=[]):
12-
super().__init__(main_thread_running, system_ready, name=name, key=key, source=source, thresholds=thresholds, trigger_active=trigger_active, frequency=frequency, actions=actions, trigger_interval=0.5)
11+
def __init__(self, main_thread_running, system_ready, name='SensorTrigger',key=None, source=None, nested_source=None, thresholds=None, channel="sensors", trigger_active=None, frequency='once', actions=[], group=None):
12+
super().__init__(main_thread_running, system_ready, name=name, key=key, source=source, thresholds=thresholds, trigger_active=trigger_active, frequency=frequency, actions=actions, trigger_interval=0.5, group=group)
1313
self.channel = channel.replace(" ", "_").lower() if channel is not None else "sensors"
1414
self.nested_source = nested_source.lower() if nested_source is not None else nested_source
1515
return
@@ -24,8 +24,9 @@ def init_trigger(self):
2424
def check(self):
2525
while self.main_thread_running.is_set():
2626
if self.system_ready.is_set():
27+
super().check()
2728
self.pubsub.get_message()
28-
self.trigger_active.clear()
29+
# self.trigger_active.clear()
2930
time.sleep(self.trigger_interval)
3031
else:
3132
time.sleep(2)

triggers/time_trigger.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
class TimeTrigger(Trigger):
1515

16-
def __init__(self, main_thread_running, system_ready, name='TimeTrigger',key=None, trigger_active=None, actions=[], schedule=None):
17-
super().__init__(main_thread_running, system_ready, name=name, key=key, trigger_active=trigger_active, actions=actions, trigger_interval=60)
16+
def __init__(self, main_thread_running, system_ready, name='TimeTrigger',key=None, trigger_active=None, actions=[], schedule=None, group=None):
17+
super().__init__(main_thread_running, system_ready, name=name, key=key, trigger_active=trigger_active, actions=actions, trigger_interval=60, group=group)
1818
self.schedule = schedule
1919
return
2020

@@ -25,10 +25,14 @@ def init_trigger(self):
2525
def check(self):
2626
while self.main_thread_running.is_set():
2727
if self.system_ready.is_set():
28+
super().check()
2829
try:
2930
if CRON_ENABLED:
3031
if pycron.is_now(self.schedule):
32+
self.trigger_active.set()
3133
super().trigger()
34+
else:
35+
self.trigger_active.clear()
3236
else:
3337
print("Error pycron not found.")
3438
except:

triggers/trigger.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88

99
class Trigger():
1010

11-
def __init__(self, main_thread_running, system_ready, name='Trigger',key=None, source=None, thresholds=None, trigger_active=None, frequency='once', actions=[], trigger_interval=1):
11+
def __init__(self, main_thread_running, system_ready, name='Trigger',key=None, source=None, thresholds=None, trigger_active=None, frequency='once', actions=[], trigger_interval=1, group=None):
1212
self.name = name
1313
self.key = key.replace(" ", "_").lower() if key is not None else self.name.replace(" ", "_").lower()
1414
self.thresholds = thresholds
1515
self.source = source.lower() if source is not None else source
16-
self.frequency = frequency
1716
self.trigger_interval = trigger_interval
1817
self.actions = actions
18+
self.group = group
19+
self.frequency = frequency if group is None else "many"
1920
# Used to check if trigger already fired without reseting
2021
self.trigger_active = trigger_active
2122
self.previous_state = trigger_active.is_set()
@@ -30,6 +31,8 @@ def init_trigger(self):
3031

3132
def check(self):
3233
#Main trigger check loop to do things like fetch messages or check time
34+
if self.group is not None:
35+
self.group.check_group()
3336
return
3437

3538
def run(self):
@@ -39,9 +42,12 @@ def run(self):
3942

4043
def trigger(self, value=None):
4144
try:
42-
# Trigger the actions of the trigger
43-
for action in self.actions:
44-
action.trigger(value)
45+
if self.group is None:
46+
# Trigger the actions of the trigger
47+
for action in self.actions:
48+
action.trigger(value)
49+
else:
50+
self.group.trigger()
4551
except Exception as e:
4652
print("Error triggering action {0} ".format(self.key), e)
4753
pass

triggers/trigger_group.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import time
2+
import json
3+
import redis
4+
import threading
5+
import sys
6+
sys.path.append('..')
7+
import variables
8+
9+
class TriggerGroup():
10+
11+
def __init__(self, name='TriggerGroup', key=None, triggers=[], group_active=None, frequency='once', actions=[]):
12+
self.name = name
13+
self.key = key.replace(" ", "_").lower() if key is not None else self.name.replace(" ", "_").lower()
14+
self.frequency = frequency
15+
self.actions = actions
16+
# Used to check if trigger already fired without reseting
17+
self.group_active = group_active if group_active is not None else threading.Event()
18+
self.previous_state = self.group_active.is_set()
19+
self.trigger_count = 0
20+
self.triggers = triggers
21+
return
22+
23+
def add_trigger(self, trigger):
24+
self.triggers.append(trigger)
25+
pass
26+
27+
def check_group(self):
28+
group_check = True
29+
for trigger in self.triggers:
30+
if not trigger.trigger_active.is_set():
31+
group_check = False
32+
if group_check:
33+
self.group_active.set()
34+
else:
35+
self.group_active.clear()
36+
self.trigger_count = 0
37+
self.previous_state = self.group_active.is_set()
38+
return group_check
39+
40+
def trigger(self, value=None):
41+
try:
42+
if self.check_group():
43+
self.trigger_count+=1
44+
if self.trigger_count == 1:
45+
for action in self.actions:
46+
action.trigger(value)
47+
else:
48+
if self.frequency == 'many':
49+
for action in self.actions:
50+
action.trigger(value)
51+
else:
52+
self.trigger_count = 0
53+
except Exception as e:
54+
print("Error triggering group {0} ".format(self.key), e)
55+
pass
56+
return
57+
58+
def shutdown(self):
59+
#Put any closing functions here that should be called as MudPi shutsdown (i.e. close connections)
60+
return

workers/trigger_worker.py

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import threading
66
import sys
77
sys.path.append('..')
8+
from triggers.trigger_group import TriggerGroup
89

910
import variables
1011
import importlib
@@ -37,67 +38,90 @@ def dynamic_import(self, path):
3738
def init_triggers(self):
3839
trigger_index = 0
3940
for trigger in self.config:
40-
if trigger.get('type', None) is not None:
41-
#Get the trigger from the triggers folder {trigger name}_trigger.{SensorName}Sensor
42-
trigger_type = 'triggers.' + trigger.get('type').lower() + '_trigger.' + trigger.get('type').capitalize() + 'Trigger'
41+
if trigger.get("triggers", False):
42+
# Load a trigger group
4343

44-
imported_trigger = self.dynamic_import(trigger_type)
44+
trigger_actions = []
45+
if trigger.get('actions'):
46+
for action in trigger.get("actions"):
47+
trigger_actions.append(self.actions[action])
4548

46-
trigger_state = {
47-
"active": threading.Event() #Event to signal relay to open/close
48-
}
49+
new_trigger_group = TriggerGroup(name=trigger.get("group"), actions=trigger_actions, frequency=trigger.get("frequency", "once"))
50+
51+
for trigger in trigger.get("triggers"):
52+
new_trigger = self.init_trigger(trigger, trigger_index, group=new_trigger_group)
53+
self.triggers.append(new_trigger)
54+
new_trigger_group.add_trigger(new_trigger)
55+
#Start the trigger thread
56+
trigger_thread = new_trigger.run()
57+
self.trigger_threads.append(trigger_thread)
58+
trigger_index += 1
59+
else:
60+
new_trigger = self.init_trigger(trigger, trigger_index)
61+
self.triggers.append(new_trigger)
62+
#Start the trigger thread
63+
trigger_thread = new_trigger.run()
64+
self.trigger_threads.append(trigger_thread)
65+
trigger_index += 1
66+
# print('{type} - {name}...\t\t\033[1;32m Listening\033[0;0m'.format(**trigger))
67+
return
4968

50-
self.trigger_events[trigger.get("key", trigger_index)] = trigger_state
69+
def init_trigger(self, config, trigger_index, group=None):
70+
if config.get('type', None) is not None:
71+
#Get the trigger from the triggers folder {trigger name}_trigger.{SensorName}Sensor
72+
trigger_type = 'triggers.' + config.get('type').lower() + '_trigger.' + config.get('type').capitalize() + 'Trigger'
5173

52-
# Define default kwargs for all trigger types, conditionally include optional variables below if they exist
53-
trigger_kwargs = {
54-
'name' : trigger.get('name', trigger.get('type')),
55-
'key' : trigger.get('key', None),
56-
'trigger_active' : trigger_state["active"],
57-
'main_thread_running' : self.main_thread_running,
58-
'system_ready' : self.system_ready
59-
}
74+
imported_trigger = self.dynamic_import(trigger_type)
6075

61-
# optional trigger variables
62-
if trigger.get('actions'):
63-
trigger_actions = []
64-
for action in trigger.get("actions"):
65-
trigger_actions.append(self.actions[action])
66-
trigger_kwargs['actions'] = trigger_actions
76+
trigger_state = {
77+
"active": threading.Event() #Event to signal relay to open/close
78+
}
6779

68-
if trigger.get('frequency'):
69-
trigger_kwargs['frequency'] = trigger.get('frequency')
80+
self.trigger_events[config.get("key", trigger_index)] = trigger_state
7081

71-
if trigger.get('schedule'):
72-
trigger_kwargs['schedule'] = trigger.get('schedule')
82+
# Define default kwargs for all trigger types, conditionally include optional variables below if they exist
83+
trigger_kwargs = {
84+
'name' : config.get('name', config.get('type')),
85+
'key' : config.get('key', None),
86+
'trigger_active' : trigger_state["active"],
87+
'main_thread_running' : self.main_thread_running,
88+
'system_ready' : self.system_ready
89+
}
7390

74-
if trigger.get('source'):
75-
trigger_kwargs['source'] = trigger.get('source')
91+
# optional trigger variables
92+
if config.get('actions'):
93+
trigger_actions = []
94+
for action in config.get("actions"):
95+
trigger_actions.append(self.actions[action])
96+
trigger_kwargs['actions'] = trigger_actions
7697

77-
if trigger.get('nested_source'):
78-
trigger_kwargs['nested_source'] = trigger.get('nested_source')
98+
if config.get('frequency'):
99+
trigger_kwargs['frequency'] = config.get('frequency')
79100

80-
if trigger.get('channel'):
81-
trigger_kwargs['channel'] = trigger.get('channel')
101+
if config.get('schedule'):
102+
trigger_kwargs['schedule'] = config.get('schedule')
82103

83-
if trigger.get('thresholds'):
84-
trigger_kwargs['thresholds'] = trigger.get('thresholds')
104+
if config.get('source'):
105+
trigger_kwargs['source'] = config.get('source')
85106

86-
new_trigger = imported_trigger(**trigger_kwargs)
87-
new_trigger.init_trigger()
107+
if config.get('nested_source'):
108+
trigger_kwargs['nested_source'] = config.get('nested_source')
88109

89-
new_trigger.type = trigger.get('type').lower()
110+
if config.get('channel'):
111+
trigger_kwargs['channel'] = config.get('channel')
90112

91-
self.triggers.append(new_trigger)
113+
if config.get('thresholds'):
114+
trigger_kwargs['thresholds'] = config.get('thresholds')
92115

93-
#Start the trigger thread
94-
trigger_thread = new_trigger.run()
116+
if group is not None:
117+
trigger_kwargs['group'] = group
95118

96-
self.trigger_threads.append(trigger_thread)
119+
new_trigger = imported_trigger(**trigger_kwargs)
120+
new_trigger.init_trigger()
97121

98-
trigger_index += 1
99-
# print('{type} - {name}...\t\t\033[1;32m Listening\033[0;0m'.format(**trigger))
100-
return
122+
new_trigger.type = config.get('type').lower()
123+
124+
return new_trigger
101125

102126
def run(self):
103127
t = threading.Thread(target=self.work, args=())

0 commit comments

Comments
 (0)