Skip to content

Commit

Permalink
v0.8.6 Added Trigger Groups
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
olixr committed Oct 31, 2019
1 parent 86d3911 commit 371541a
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 67 deletions.
37 changes: 28 additions & 9 deletions mudpi.config.example
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,35 @@
],
"triggers": [
{
"type": "control",
"source": "button_1",
"key": "button_1_trigger",
"name": "Override Button Pressed",
"actions": ["turn_on_lights_1", "save_to_file"],
"frequency":"once",
"thresholds": [
"group":"Trigger Group 1",
"actions": ["turn_on_lights_1"],
"triggers":[
{
"comparison":"eq",
"value":true
"type": "control",
"source": "button_1",
"key": "button_1_trigger",
"name": "Override Button Pressed",
"frequency":"once",
"thresholds": [
{
"comparison":"eq",
"value":true
}
]
},
{
"type": "sensor",
"source": "weather",
"nested_source":"temperature",
"key": "temp_trigger",
"name": "Temp too hot",
"frequency":"once",
"thresholds": [
{
"comparison":"gte",
"value":70
}
]
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion mudpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
print('_________________________________________________')
print('')
print('Eric Davisson @theDavisson')
print('Version: ', CONFIGS.get('version', '0.8.5'))
print('Version: ', CONFIGS.get('version', '0.8.6'))
print('\033[0;0m')

if CONFIGS['debug'] is True:
Expand Down
7 changes: 4 additions & 3 deletions triggers/control_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

class ControlTrigger(Trigger):

def __init__(self, main_thread_running, system_ready, name='ControlTrigger',key=None, source=None, thresholds=None, channel="controls", trigger_active=None, frequency='once', actions=[]):
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)
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):
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)
self.channel = channel.replace(" ", "_").lower() if channel is not None else "controls"
return

Expand All @@ -23,8 +23,9 @@ def init_trigger(self):
def check(self):
while self.main_thread_running.is_set():
if self.system_ready.is_set():
super().check()
self.pubsub.get_message()
self.trigger_active.clear()
# self.trigger_active.clear()
time.sleep(self.trigger_interval)
else:
time.sleep(2)
Expand Down
7 changes: 4 additions & 3 deletions triggers/sensor_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

class SensorTrigger(Trigger):

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=[]):
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)
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):
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)
self.channel = channel.replace(" ", "_").lower() if channel is not None else "sensors"
self.nested_source = nested_source.lower() if nested_source is not None else nested_source
return
Expand All @@ -24,8 +24,9 @@ def init_trigger(self):
def check(self):
while self.main_thread_running.is_set():
if self.system_ready.is_set():
super().check()
self.pubsub.get_message()
self.trigger_active.clear()
# self.trigger_active.clear()
time.sleep(self.trigger_interval)
else:
time.sleep(2)
Expand Down
8 changes: 6 additions & 2 deletions triggers/time_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

class TimeTrigger(Trigger):

def __init__(self, main_thread_running, system_ready, name='TimeTrigger',key=None, trigger_active=None, actions=[], schedule=None):
super().__init__(main_thread_running, system_ready, name=name, key=key, trigger_active=trigger_active, actions=actions, trigger_interval=60)
def __init__(self, main_thread_running, system_ready, name='TimeTrigger',key=None, trigger_active=None, actions=[], schedule=None, group=None):
super().__init__(main_thread_running, system_ready, name=name, key=key, trigger_active=trigger_active, actions=actions, trigger_interval=60, group=group)
self.schedule = schedule
return

Expand All @@ -25,10 +25,14 @@ def init_trigger(self):
def check(self):
while self.main_thread_running.is_set():
if self.system_ready.is_set():
super().check()
try:
if CRON_ENABLED:
if pycron.is_now(self.schedule):
self.trigger_active.set()
super().trigger()
else:
self.trigger_active.clear()
else:
print("Error pycron not found.")
except:
Expand Down
16 changes: 11 additions & 5 deletions triggers/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@

class Trigger():

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):
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):
self.name = name
self.key = key.replace(" ", "_").lower() if key is not None else self.name.replace(" ", "_").lower()
self.thresholds = thresholds
self.source = source.lower() if source is not None else source
self.frequency = frequency
self.trigger_interval = trigger_interval
self.actions = actions
self.group = group
self.frequency = frequency if group is None else "many"
# Used to check if trigger already fired without reseting
self.trigger_active = trigger_active
self.previous_state = trigger_active.is_set()
Expand All @@ -30,6 +31,8 @@ def init_trigger(self):

def check(self):
#Main trigger check loop to do things like fetch messages or check time
if self.group is not None:
self.group.check_group()
return

def run(self):
Expand All @@ -39,9 +42,12 @@ def run(self):

def trigger(self, value=None):
try:
# Trigger the actions of the trigger
for action in self.actions:
action.trigger(value)
if self.group is None:
# Trigger the actions of the trigger
for action in self.actions:
action.trigger(value)
else:
self.group.trigger()
except Exception as e:
print("Error triggering action {0} ".format(self.key), e)
pass
Expand Down
60 changes: 60 additions & 0 deletions triggers/trigger_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import time
import json
import redis
import threading
import sys
sys.path.append('..')
import variables

class TriggerGroup():

def __init__(self, name='TriggerGroup', key=None, triggers=[], group_active=None, frequency='once', actions=[]):
self.name = name
self.key = key.replace(" ", "_").lower() if key is not None else self.name.replace(" ", "_").lower()
self.frequency = frequency
self.actions = actions
# Used to check if trigger already fired without reseting
self.group_active = group_active if group_active is not None else threading.Event()
self.previous_state = self.group_active.is_set()
self.trigger_count = 0
self.triggers = triggers
return

def add_trigger(self, trigger):
self.triggers.append(trigger)
pass

def check_group(self):
group_check = True
for trigger in self.triggers:
if not trigger.trigger_active.is_set():
group_check = False
if group_check:
self.group_active.set()
else:
self.group_active.clear()
self.trigger_count = 0
self.previous_state = self.group_active.is_set()
return group_check

def trigger(self, value=None):
try:
if self.check_group():
self.trigger_count+=1
if self.trigger_count == 1:
for action in self.actions:
action.trigger(value)
else:
if self.frequency == 'many':
for action in self.actions:
action.trigger(value)
else:
self.trigger_count = 0
except Exception as e:
print("Error triggering group {0} ".format(self.key), e)
pass
return

def shutdown(self):
#Put any closing functions here that should be called as MudPi shutsdown (i.e. close connections)
return
112 changes: 68 additions & 44 deletions workers/trigger_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import threading
import sys
sys.path.append('..')
from triggers.trigger_group import TriggerGroup

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

imported_trigger = self.dynamic_import(trigger_type)
trigger_actions = []
if trigger.get('actions'):
for action in trigger.get("actions"):
trigger_actions.append(self.actions[action])

trigger_state = {
"active": threading.Event() #Event to signal relay to open/close
}
new_trigger_group = TriggerGroup(name=trigger.get("group"), actions=trigger_actions, frequency=trigger.get("frequency", "once"))

for trigger in trigger.get("triggers"):
new_trigger = self.init_trigger(trigger, trigger_index, group=new_trigger_group)
self.triggers.append(new_trigger)
new_trigger_group.add_trigger(new_trigger)
#Start the trigger thread
trigger_thread = new_trigger.run()
self.trigger_threads.append(trigger_thread)
trigger_index += 1
else:
new_trigger = self.init_trigger(trigger, trigger_index)
self.triggers.append(new_trigger)
#Start the trigger thread
trigger_thread = new_trigger.run()
self.trigger_threads.append(trigger_thread)
trigger_index += 1
# print('{type} - {name}...\t\t\033[1;32m Listening\033[0;0m'.format(**trigger))
return

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

# Define default kwargs for all trigger types, conditionally include optional variables below if they exist
trigger_kwargs = {
'name' : trigger.get('name', trigger.get('type')),
'key' : trigger.get('key', None),
'trigger_active' : trigger_state["active"],
'main_thread_running' : self.main_thread_running,
'system_ready' : self.system_ready
}
imported_trigger = self.dynamic_import(trigger_type)

# optional trigger variables
if trigger.get('actions'):
trigger_actions = []
for action in trigger.get("actions"):
trigger_actions.append(self.actions[action])
trigger_kwargs['actions'] = trigger_actions
trigger_state = {
"active": threading.Event() #Event to signal relay to open/close
}

if trigger.get('frequency'):
trigger_kwargs['frequency'] = trigger.get('frequency')
self.trigger_events[config.get("key", trigger_index)] = trigger_state

if trigger.get('schedule'):
trigger_kwargs['schedule'] = trigger.get('schedule')
# Define default kwargs for all trigger types, conditionally include optional variables below if they exist
trigger_kwargs = {
'name' : config.get('name', config.get('type')),
'key' : config.get('key', None),
'trigger_active' : trigger_state["active"],
'main_thread_running' : self.main_thread_running,
'system_ready' : self.system_ready
}

if trigger.get('source'):
trigger_kwargs['source'] = trigger.get('source')
# optional trigger variables
if config.get('actions'):
trigger_actions = []
for action in config.get("actions"):
trigger_actions.append(self.actions[action])
trigger_kwargs['actions'] = trigger_actions

if trigger.get('nested_source'):
trigger_kwargs['nested_source'] = trigger.get('nested_source')
if config.get('frequency'):
trigger_kwargs['frequency'] = config.get('frequency')

if trigger.get('channel'):
trigger_kwargs['channel'] = trigger.get('channel')
if config.get('schedule'):
trigger_kwargs['schedule'] = config.get('schedule')

if trigger.get('thresholds'):
trigger_kwargs['thresholds'] = trigger.get('thresholds')
if config.get('source'):
trigger_kwargs['source'] = config.get('source')

new_trigger = imported_trigger(**trigger_kwargs)
new_trigger.init_trigger()
if config.get('nested_source'):
trigger_kwargs['nested_source'] = config.get('nested_source')

new_trigger.type = trigger.get('type').lower()
if config.get('channel'):
trigger_kwargs['channel'] = config.get('channel')

self.triggers.append(new_trigger)
if config.get('thresholds'):
trigger_kwargs['thresholds'] = config.get('thresholds')

#Start the trigger thread
trigger_thread = new_trigger.run()
if group is not None:
trigger_kwargs['group'] = group

self.trigger_threads.append(trigger_thread)
new_trigger = imported_trigger(**trigger_kwargs)
new_trigger.init_trigger()

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

return new_trigger

def run(self):
t = threading.Thread(target=self.work, args=())
Expand Down

0 comments on commit 371541a

Please sign in to comment.