diff --git a/README.md b/README.md
index 814ce76..aeb3ad9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@

# MudPi Smart Garden
-> A python library to gather sensor readings, trigger components, control solenoids and more in an event based system that can be run on a raspberry pi.
+> A python library to gather sensor readings, trigger components, control solenoids and more in an event based system that can be run on a linux SBC, including Raspberry Pi.
## Documentation
@@ -17,7 +17,7 @@ For examples and guides on how to setup and use MudPi check out the [free guides
## Contributing
-Any contributions you can make will be greatly appreciated. If you are interested in contributing please get in touch with me and submit a pull request. There is much more I would like to add support for, however being a single developer limits my scope. Therefore mainly bugs will be accepted as issues.
+Any contributions you can make will be greatly appreciated. If you are interested in contributing please get in touch with me and submit a pull request. There is much more I would like to add support for, however being a single developer limits my scope. Therefore mainly bugs will be accepted as issues.
## Versioning
@@ -33,7 +33,7 @@ Breaking.Major.Minor
* [Twitter.com/MudpiApp](https://twitter.com/mudpiapp)
## Hardware Tested On
-These are the devices and sensors I tested and used with MudPi successfully. Many sensors are similar so it will work with a range more than what is listed below.
+These are the devices and sensors I tested and used with MudPi successfully. Many sensors are similar so it will work with a range more than what is listed below.
**Devices**
* [Raspberry Pi 2 Model B+](https://www.raspberrypi.org/products/raspberry-pi-2-model-b/)
@@ -53,6 +53,7 @@ These are the devices and sensors I tested and used with MudPi successfully. Man
* [DS18B20 Temperature Sensors](https://www.amazon.com/gp/product/B018KFX5X0/ref=oh_aui_detailpage_o08_s00?ie=UTF8&psc=1)
* [DHT11 Temperature/Humidity Sensor](https://www.amazon.com/gp/product/B01DKC2GQ0/ref=oh_aui_detailpage_o07_s05?ie=UTF8&psc=1)
* [DHT22 Temperature/Humidity Sensor](https://www.dfrobot.com/product-1102.html)
+* [T9602 Temperature/Humidity Sensor](https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3224-t9602)
* [Rain Sensor](https://www.amazon.com/gp/product/B01D9JK2F6/ref=oh_aui_detailpage_o03_s00?ie=UTF8&psc=1)
* [Ambient Light Sensor](https://www.dfrobot.com/product-1004.html)
* [DFROBOT Analog Capacitive Soil Moisture Sensor](https://www.amazon.com/gp/product/B01GHY0N4K/ref=oh_aui_detailpage_o01_s00?ie=UTF8&psc=1)
@@ -60,6 +61,7 @@ These are the devices and sensors I tested and used with MudPi successfully. Man
**Components**
* [4 Channel DC 5V Relay](https://www.amazon.com/gp/product/B00KTEN3TM/ref=oh_aui_detailpage_o08_s00?ie=UTF8&psc=1)
+* [8 Channel DC 5V Relay](https://www.amazon.co.uk/SODIAL-Channel-Module-Arduino-Electronic/dp/B00IIDXYTA)
* [LCD 16 x 2 Display I2C](https://www.dfrobot.com/product-135.html)
* [LCD 16 x 2 Display I2C - PCF8574](https://www.amazon.com/Arducam-Display-Controller-Character-Backlight/dp/B019D9TYMI)
* [LCD 20 x 4 Display I2C](https://www.dfrobot.com/product-590.html)
@@ -74,4 +76,3 @@ This project is licensed under the BSD-4-Clause License - see the [LICENSE.md](L
-
diff --git a/action.py b/action.py
index 865855a..bbacbcd 100644
--- a/action.py
+++ b/action.py
@@ -1,45 +1,54 @@
-import time
import json
-import redis
import subprocess
import sys
-sys.path.append('..')
+
+import redis
+
+
+
class Action():
- def __init__(self, config):
- self.config = config
- self.name = config.get("name", "Action")
- self.type = config.get("type", "event")
- self.key = config.get("key", None).replace(" ", "_").lower() if config.get("key") is not None else self.name.replace(" ", "_").lower()
- # Actions will be either objects to publish for events or a command string to execute
- self.action = config.get("action")
- try:
- self.r = config["redis"] if config["redis"] is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- return
-
- def init_action(self):
- if self.type == 'event':
- self.topic = self.config.get("topic", "mudpi")
- elif self.type == 'command':
- self.shell = self.config.get("shell", False)
-
- def trigger(self, value=None):
- if self.type == 'event':
- self.emitEvent()
- elif self.type == 'command':
- self.runCommand(value)
- return
-
- def emitEvent(self):
- self.r.publish(self.topic, json.dumps(self.action))
- return
-
- def runCommand(self, value=None):
- if value is None:
- completed_process = subprocess.run([self.action], shell=self.shell)
- else:
- completed_process = subprocess.run([self.action, json.dumps(value)], shell=self.shell)
- return
\ No newline at end of file
+ def __init__(self, config):
+ self.config = config
+ self.name = config.get("name", "Action")
+ self.type = config.get("type", "event")
+ self.key = config.get("key", None).replace(" ",
+ "_").lower() if config.get(
+ "key") is not None else self.name.replace(" ", "_").lower()
+ # Actions will be either objects to publish for events
+ # or a command string to execute
+ self.action = config.get("action")
+
+ try:
+ self.r = config["redis"] if config[
+ "redis"] is not None else redis.Redis(
+ host='127.0.0.1', port=6379)
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+ return
+
+ def init_action(self):
+ if self.type == 'event':
+ self.topic = self.config.get("topic", "mudpi")
+ elif self.type == 'command':
+ self.shell = self.config.get("shell", False)
+
+ def trigger(self, value=None):
+ if self.type == 'event':
+ self.emit_event()
+ elif self.type == 'command':
+ self.run_command(value)
+ return
+
+ def emit_event(self):
+ self.r.publish(self.topic, json.dumps(self.action))
+ return
+
+ def run_command(self, value=None):
+ if value is None:
+ completed_process = subprocess.run([self.action], shell=self.shell)
+ else:
+ completed_process = subprocess.run(
+ [self.action, json.dumps(value)], shell=self.shell)
+ return
diff --git a/config_load.py b/config_load.py
deleted file mode 100644
index 11b7774..0000000
--- a/config_load.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import json
-
-def loadConfigJson():
- configs = {}
- with open('mudpi.config') as loadedfile:
- configs = json.load(loadedfile)
- loadedfile.close()
- return configs
\ No newline at end of file
diff --git a/constants.py b/constants.py
new file mode 100644
index 0000000..c33e7ee
--- /dev/null
+++ b/constants.py
@@ -0,0 +1,5 @@
+PREVIOUS_LINE = "\x1b[1F"
+RED_BACK = "\x1b[41;37m"
+GREEN_BACK = "\x1b[42;30m"
+YELLOW_BACK = "\x1b[43;30m"
+RESET = "\x1b[0m"
diff --git a/controls/arduino/button_control.py b/controls/arduino/button_control.py
index cab3c1c..c0cab0d 100644
--- a/controls/arduino/button_control.py
+++ b/controls/arduino/button_control.py
@@ -10,30 +10,30 @@
class ButtonControl(Control):
- def __init__(self, pin, name=None, key=None, connection=default_connection, analog_pin_mode=False, topic=None, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, analog_pin_mode=analog_pin_mode, redis_conn=redis_conn)
- self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
- self.state_counter = 3
- self.previous_state = 0
- return
+ def __init__(self, pin, name=None, key=None, connection=default_connection, analog_pin_mode=False, topic=None, redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection, analog_pin_mode=analog_pin_mode, redis_conn=redis_conn)
+ self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
+ self.state_counter = 3
+ self.previous_state = 0
+ return
- def init_control(self):
- super().init_control()
+ def init_control(self):
+ super().init_control()
- def read(self):
- state = super().read()
- if state == self.previous_state:
- self.state_counter += 1
- if self.state_counter % 2 == 0:
- if state:
- super().emitEvent(1)
- elif self.state_counter == 2:
- super().emitEvent(0)
- else:
- self.state_counter = 1
+ def read(self):
+ state = super().read()
+ if state == self.previous_state:
+ self.state_counter += 1
+ if self.state_counter % 2 == 0:
+ if state:
+ super().emitEvent(1)
+ elif self.state_counter == 2:
+ super().emitEvent(0)
+ else:
+ self.state_counter = 1
- self.previous_state = state
- return state
+ self.previous_state = state
+ return state
- def readRaw(self):
- return super().read()
+ def read_raw(self):
+ return super().read()
diff --git a/controls/arduino/control.py b/controls/arduino/control.py
index 77a45ef..a7eae44 100644
--- a/controls/arduino/control.py
+++ b/controls/arduino/control.py
@@ -3,59 +3,59 @@
import redis
from nanpy import (ArduinoApi, SerialManager)
import sys
-sys.path.append('..')
+
default_connection = SerialManager()
# Base sensor class to extend all other arduino sensors from.
class Control():
- def __init__(self, pin, name=None, connection=default_connection, analog_pin_mode=False, key=None, redis_conn=None):
- self.pin = pin
-
- if key is None:
- raise Exception('No "key" Found in Control Config')
- else:
- self.key = key.replace(" ", "_").lower()
-
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
-
- self.analog_pin_mode = analog_pin_mode
- self.connection = connection
- self.api = ArduinoApi(connection)
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- return
-
- def init_control(self):
- #Initialize the control here (i.e. set pin mode, get addresses, etc)
- self.api.pinMode(self.pin, self.api.INPUT)
- pass
-
- def read(self):
- #Read the sensor(s), parse the data and store it in redis if redis is configured
- return self.readPin()
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- pass
-
- def readPin(self):
- #Read the pin from the ardiuno. Can be analog or digital based on "analog_pin_mode"
- data = self.api.analogRead(self.pin) if self.analog_pin_mode else self.api.digitalRead(self.pin)
- return data
-
- def emitEvent(self, data):
- message = {
- 'event':'ControlUpdate',
- 'data': {
- self.key:data
- }
- }
- print(message["data"])
- self.r.publish('controls', json.dumps(message))
\ No newline at end of file
+ def __init__(self, pin, name=None, connection=default_connection, analog_pin_mode=False, key=None, redis_conn=None):
+ self.pin = pin
+
+ if key is None:
+ raise Exception('No "key" Found in Control Config')
+ else:
+ self.key = key.replace(" ", "_").lower()
+
+ if name is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = name
+
+ self.analog_pin_mode = analog_pin_mode
+ self.connection = connection
+ self.api = ArduinoApi(connection)
+ try:
+ self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+ return
+
+ def init_control(self):
+ #Initialize the control here (i.e. set pin mode, get addresses, etc)
+ self.api.pinMode(self.pin, self.api.INPUT)
+ pass
+
+ def read(self):
+ #Read the sensor(s), parse the data and store it in redis if redis is configured
+ return self.read_pin()
+
+ def read_raw(self):
+ #Read the sensor(s) but return the raw data, useful for debugging
+ pass
+
+ def read_pin(self):
+ #Read the pin from the ardiuno. Can be analog or digital based on "analog_pin_mode"
+ data = self.api.analogRead(self.pin) if self.analog_pin_mode else self.api.digitalRead(self.pin)
+ return data
+
+ def emitEvent(self, data):
+ message = {
+ 'event':'ControlUpdate',
+ 'data': {
+ self.key:data
+ }
+ }
+ print(message["data"])
+ self.r.publish('controls', json.dumps(message))
\ No newline at end of file
diff --git a/controls/arduino/potentiometer_control.py b/controls/arduino/potentiometer_control.py
index f0d26f7..dbf6d05 100644
--- a/controls/arduino/potentiometer_control.py
+++ b/controls/arduino/potentiometer_control.py
@@ -10,29 +10,29 @@
class PotentiometerControl(Control):
- def __init__(self, pin, name=None, key=None, connection=default_connection, analog_pin_mode=True, topic=None, reading_buffer=3, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, analog_pin_mode=analog_pin_mode, redis_conn=redis_conn)
- self.previous_state = 0
- # Reading buffer helps prevent multiple events when values are floating between small amounts
- self.reading_buffer = reading_buffer
- return
-
- def init_control(self):
- super().init_control()
- # Set initial state to prevent event on boot
- self.previous_state = super().read()
-
- def read(self):
- state = super().read()
-
- if (state < self.previous_state - self.reading_buffer) or (state > self.previous_state + self.reading_buffer):
- # Value changed
- # print('{0}: {1}'.format(self.name, state))
- super().emitEvent(state)
-
- self.previous_state = state
- return state
-
- def readRaw(self):
- return super().read()
+ def __init__(self, pin, name=None, key=None, connection=default_connection, analog_pin_mode=True, topic=None, reading_buffer=3, redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection, analog_pin_mode=analog_pin_mode, redis_conn=redis_conn)
+ self.previous_state = 0
+ # Reading buffer helps prevent multiple events when values are floating between small amounts
+ self.reading_buffer = reading_buffer
+ return
+
+ def init_control(self):
+ super().init_control()
+ # Set initial state to prevent event on boot
+ self.previous_state = super().read()
+
+ def read(self):
+ state = super().read()
+
+ if (state < self.previous_state - self.reading_buffer) or (state > self.previous_state + self.reading_buffer):
+ # Value changed
+ # print('{0}: {1}'.format(self.name, state))
+ super().emitEvent(state)
+
+ self.previous_state = state
+ return state
+
+ def read_raw(self):
+ return super().read()
diff --git a/controls/arduino/switch_control.py b/controls/arduino/switch_control.py
index 972d01c..2b4eb45 100644
--- a/controls/arduino/switch_control.py
+++ b/controls/arduino/switch_control.py
@@ -10,31 +10,31 @@
class SwitchControl(Control):
- def __init__(self, pin, name=None, key=None, connection=default_connection, analog_pin_mode=False, topic=None, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, analog_pin_mode=analog_pin_mode, redis_conn=redis_conn)
- self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
- self.state_counter = 3
- self.previous_state = 0
- self.trigger_delay = 2
- return
+ def __init__(self, pin, name=None, key=None, connection=default_connection, analog_pin_mode=False, topic=None, redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection, analog_pin_mode=analog_pin_mode, redis_conn=redis_conn)
+ self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
+ self.state_counter = 3
+ self.previous_state = 0
+ self.trigger_delay = 2
+ return
- def init_control(self):
- super().init_control()
- # Set initial state to prevent event on boot
- self.previous_state = super().read()
+ def init_control(self):
+ super().init_control()
+ # Set initial state to prevent event on boot
+ self.previous_state = super().read()
- def read(self):
- state = super().read()
- if state == self.previous_state:
- self.state_counter += 1
- if self.state_counter == self.trigger_delay:
- clean_state = 1 if state else 0
- super().emitEvent(clean_state)
- else:
- self.state_counter = 1
+ def read(self):
+ state = super().read()
+ if state == self.previous_state:
+ self.state_counter += 1
+ if self.state_counter == self.trigger_delay:
+ clean_state = 1 if state else 0
+ super().emitEvent(clean_state)
+ else:
+ self.state_counter = 1
- self.previous_state = state
- return state
+ self.previous_state = state
+ return state
- def readRaw(self):
- return super().read()
+ def read_raw(self):
+ return super().read()
diff --git a/controls/pi/__init__.py b/controls/linux/__init__.py
similarity index 100%
rename from controls/pi/__init__.py
rename to controls/linux/__init__.py
diff --git a/controls/linux/button_control.py b/controls/linux/button_control.py
new file mode 100644
index 0000000..db48cfb
--- /dev/null
+++ b/controls/linux/button_control.py
@@ -0,0 +1,30 @@
+import time
+import datetime
+import json
+import redis
+from .control import Control
+
+
+r = redis.Redis(host='127.0.0.1', port=6379)
+
+
+class ButtonControl(Control):
+
+ def __init__(self, pin, name=None, key=None, resistor=None, edge_detection=None, debounce=None, topic=None, redis_conn=None):
+ super().__init__(pin, name=name, key=key, resistor=resistor, edge_detection=edge_detection, debounce=debounce, redis_conn=redis_conn)
+ self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
+ return
+
+ def init_control(self):
+ super().init_control()
+
+ def read(self):
+ state = super().read()
+ if state:
+ # Button Pressed
+ # eventually add multipress tracking
+ super().emitEvent(1)
+ return state
+
+ def read_raw(self):
+ return super().read()
diff --git a/controls/linux/control.py b/controls/linux/control.py
new file mode 100644
index 0000000..b7339ce
--- /dev/null
+++ b/controls/linux/control.py
@@ -0,0 +1,99 @@
+import time
+import json
+import redis
+import sys
+import board
+import digitalio
+from adafruit_debouncer import Debouncer
+
+
+
+
+
+# Base sensor class to extend all other arduino sensors from.
+class Control():
+
+ def __init__(self, pin, name=None, key=None, resistor=None, edge_detection=None, debounce=None, redis_conn=None):
+ self.pin_obj = getattr(board, pin)
+
+ if key is None:
+ raise Exception('No "key" Found in Control Config')
+ else:
+ self.key = key.replace(" ", "_").lower()
+
+ if name is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = name
+
+ self.gpio = digitalio
+ self.debounce = debounce if debounce is not None else None
+ try:
+ self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+
+ if resistor is not None:
+ if resistor == "up" or resistor == digitalio.Pull.UP:
+ self.resistor = digitalio.Pull.UP
+ elif resistor == "down" or resistor == digitalio.Pull.DOWN:
+ self.resistor = digitalio.Pull.DOWN
+ else:
+ self.resistor = resistor
+
+ if edge_detection is not None:
+ if edge_detection == "falling" or edge_detection == "fell":
+ self.edge_detection = "fell"
+ elif edge_detection == "rising" or edge_detection == "rose":
+ self.edge_detection = "rose"
+ elif edge_detection == "both":
+ self.edge_detection = "both"
+ else:
+ self.edge_detection = None
+
+ return
+
+ def init_control(self):
+ """Initialize the control here (i.e. set pin mode, get addresses, etc)
+ Set the Pin for the button with the internal pull up resistor"""
+ self.control_pin = self.gpio.DigitalInOut(self.pin_obj)
+ self.control_pin.switch_to_input(pull=self.resistor)
+ # If edge detection has been configured lets take advantage of that
+ if self.edge_detection is not None:
+ self.button = Debouncer(self.control_pin)
+ if self.debounce is not None:
+ self.button.interval = self.debounce
+
+ def read(self):
+ """Read the sensor(s), parse the data and store it in redis if redis is configured
+ If edge detection is being used return the detection event instead"""
+ if self.edge_detection is not None:
+ self.button.update()
+ if self.edge_detection == "both":
+ if self.button.fell or self.button.rose:
+ return True
+ else:
+ return False
+ else: # "fell" or "rose"
+ return getattr(self.button, self.edge_detection)
+ else:
+ return None
+
+ def read_raw(self):
+ """Read the sensor(s) but return the raw data, useful for debugging"""
+ pass
+
+ def read_pin(self):
+ """Read the pin from the board digital reads only"""
+ data = self.gpio.DigitalInOut(self.pin_obj).value
+ return data
+
+ def emitEvent(self, data):
+ message = {
+ 'event': 'ControlUpdate',
+ 'data': {
+ self.key: data
+ }
+ }
+ print(message["data"])
+ self.r.publish('controls', json.dumps(message))
diff --git a/controls/linux/switch_control.py b/controls/linux/switch_control.py
new file mode 100644
index 0000000..154585a
--- /dev/null
+++ b/controls/linux/switch_control.py
@@ -0,0 +1,42 @@
+import time
+import datetime
+import json
+import redis
+from .control import Control
+
+
+r = redis.Redis(host='127.0.0.1', port=6379)
+
+
+class SwitchControl(Control):
+
+ def __init__(self, pin, name=None, key=None, resistor=None, edge_detection=None, debounce=None, topic=None, redis_conn=None):
+ super().__init__(pin, name=name, key=key, resistor=resistor, edge_detection=edge_detection, debounce=debounce, redis_conn=redis_conn)
+ self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
+ # Keep counter 1 above delay to avoid event on boot
+ self.state_counter = 3
+ self.previous_state = 0
+ self.trigger_delay = 2
+ return
+
+ def init_control(self):
+ super().init_control()
+ # Get current state on boot
+ self.previous_state = super().read()
+
+ def read(self):
+ state = super().read()
+ if state == self.previous_state:
+ self.state_counter += 1
+ if self.state_counter == self.trigger_delay:
+ clean_state = 1 if state else 0
+ super().emitEvent(clean_state)
+ else:
+ # Button State Changed
+ self.state_counter = 1
+
+ self.previous_state = state
+ return state
+
+ def read_raw(self):
+ return super().read()
diff --git a/controls/pi/button_control.py b/controls/pi/button_control.py
deleted file mode 100644
index f79075a..0000000
--- a/controls/pi/button_control.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import time
-import datetime
-import json
-import redis
-from .control import Control
-import RPi.GPIO as GPIO
-
-r = redis.Redis(host='127.0.0.1', port=6379)
-
-class ButtonControl(Control):
-
- def __init__(self, pin, name=None, key=None, resistor=None, edge_detection=None, debounce=None, topic=None, redis_conn=None):
- super().__init__(pin, name=name, key=key, resistor=resistor, edge_detection=edge_detection, debounce=debounce, redis_conn=redis_conn)
- self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
- return
-
- def init_control(self):
- super().init_control()
-
- def read(self):
- state = super().read()
- if state:
- #Button Pressed
- #eventually add multipress tracking
- super().emitEvent(1)
- return state
-
- def readRaw(self):
- return super().read()
-
diff --git a/controls/pi/control.py b/controls/pi/control.py
deleted file mode 100644
index fc5c35f..0000000
--- a/controls/pi/control.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import time
-import json
-import redis
-import RPi.GPIO as GPIO
-import sys
-sys.path.append('..')
-
-# Base sensor class to extend all other arduino sensors from.
-class Control():
-
- def __init__(self, pin, name=None, key=None, resistor=None, edge_detection=None, debounce=None, redis_conn=None):
- self.pin = pin
-
- if key is None:
- raise Exception('No "key" Found in Control Config')
- else:
- self.key = key.replace(" ", "_").lower()
-
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
-
- self.gpio = GPIO
- self.debounce = debounce if debounce is not None else None
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
-
- if resistor is not None:
- if resistor == "up" or resistor == GPIO.PUD_UP:
- self.resistor = GPIO.PUD_UP
- elif resistor == "down" or resistor == GPIO.PUD_DOWN:
- self.resistor = GPIO.PUD_DOWN
- else:
- self.resistor = resistor
-
- if edge_detection is not None:
- if edge_detection == "falling" or edge_detection == GPIO.FALLING:
- self.edge_detection = GPIO.FALLING
- elif edge_detection == "rising" or edge_detection == GPIO.RISING:
- self.edge_detection = GPIO.RISING
- elif edge_detection == "both" or edge_detection == GPIO.BOTH:
- self.edge_detection = GPIO.BOTH
- else:
- self.edge_detection = None
-
- return
-
- def init_control(self):
- #Initialize the control here (i.e. set pin mode, get addresses, etc)
- #Set the Pin for the button with the internal pull up resistor
- self.gpio.setup(self.pin, GPIO.IN, pull_up_down=self.resistor)
- # If edge detection has been configured lets take advantage of that
- if self.edge_detection is not None:
- GPIO.add_event_detect(self.pin, self.edge_detection, bouncetime=self.debounce)
- pass
-
- def read(self):
- #Read the sensor(s), parse the data and store it in redis if redis is configured
- #If edge detection is being used return the detection event instead
- return self.readPin() if self.edge_detection is None else GPIO.event_detected(self.pin)
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- pass
-
- def readPin(self):
- #Read the pin from the pi digital reads only
- data = self.gpio.input(self.pin)
- return data
-
- def emitEvent(self, data):
- message = {
- 'event':'ControlUpdate',
- 'data': {
- self.key:data
- }
- }
- print(message["data"])
- self.r.publish('controls', json.dumps(message))
\ No newline at end of file
diff --git a/controls/pi/switch_control.py b/controls/pi/switch_control.py
deleted file mode 100644
index e2ffc21..0000000
--- a/controls/pi/switch_control.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import time
-import datetime
-import json
-import redis
-from .control import Control
-import RPi.GPIO as GPIO
-
-r = redis.Redis(host='127.0.0.1', port=6379)
-
-class SwitchControl(Control):
-
- def __init__(self, pin, name=None, key=None, resistor=None, edge_detection=None, debounce=None, topic=None, redis_conn=None):
- super().__init__(pin, name=name, key=key, resistor=resistor, edge_detection=edge_detection, debounce=debounce, redis_conn=redis_conn)
- self.topic = topic.replace(" ", "/").lower() if topic is not None else 'mudpi/controls/'+self.key
- # Keep counter 1 above delay to avoid event on boot
- self.state_counter = 3
- self.previous_state = 0
- self.trigger_delay = 2
- return
-
- def init_control(self):
- super().init_control()
- # Get current state on boot
- self.previous_state = super().read()
-
- def read(self):
- state = super().read()
- if state == self.previous_state:
- self.state_counter += 1
- if self.state_counter == self.trigger_delay:
- clean_state = 1 if state else 0
- super().emitEvent(clean_state)
- else:
- #Button State Changed
- self.state_counter = 1
-
- self.previous_state = state
- return state
-
- def readRaw(self):
- return super().read()
-
diff --git a/logger/Logger.py b/logger/Logger.py
index da37cc0..3d83379 100644
--- a/logger/Logger.py
+++ b/logger/Logger.py
@@ -1,7 +1,6 @@
import logging
import sys
-
LOG_LEVEL = {
"unknown": logging.NOTSET,
"debug": logging.DEBUG,
@@ -11,8 +10,8 @@
"error": logging.ERROR,
}
-class Logger:
+class Logger:
logger = None
def __init__(self, config: dict):
@@ -25,24 +24,28 @@ def __init__(self, config: dict):
"terminal_log_level": "info"
}
# raise Exception("No Logger configs were found!")
-
- self.__log = logging.getLogger(config['name']+"_stream")
+
+ self.__log = logging.getLogger(config['name'] + "_stream")
self.__file_log = logging.getLogger(config['name'])
try:
- file_log_level = LOG_LEVEL[logger_config["file_log_level"]] if not config["debug"] else LOG_LEVEL["debug"]
- stream_log_level = LOG_LEVEL[logger_config["terminal_log_level"]] if not config["debug"] else LOG_LEVEL["debug"]
+ file_log_level = LOG_LEVEL[logger_config["file_log_level"]] if not \
+ config["debug"] else LOG_LEVEL["debug"]
+ stream_log_level = LOG_LEVEL[
+ logger_config["terminal_log_level"]] if not config[
+ "debug"] else LOG_LEVEL["debug"]
except KeyError:
file_log_level = LOG_LEVEL["unknown"]
stream_log_level = LOG_LEVEL["unknown"]
-
+
self.__log.setLevel(stream_log_level)
self.__file_log.setLevel(file_log_level)
try:
try:
if len(logger_config['file']) != 0:
- open(logger_config['file'], 'w').close() # testing file path
+ open(logger_config['file'],
+ 'w').close() # testing file path
file_handler = logging.FileHandler(logger_config['file'])
file_handler.setLevel(file_log_level)
@@ -51,16 +54,19 @@ def __init__(self, config: dict):
self.WRITE_TO_FILE = False
except FileNotFoundError:
self.WRITE_TO_FILE = False
-
+
except KeyError as e:
self.WRITE_TO_FILE = False
- self.log(LOG_LEVEL["warning"], "File Handler could not be started due to a KeyError: {0}".format(e))
-
+ self.log(LOG_LEVEL["warning"],
+ "File Handler could not be started due to a KeyError: {0}".format(
+ e))
+
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(stream_log_level)
- file_formatter = logging.Formatter("[%(asctime)s][%(name)s][%(levelname)s] %(message)s", "%H:%M:%S")
+ file_formatter = logging.Formatter(
+ "[%(asctime)s][%(name)s][%(levelname)s] %(message)s", "%H:%M:%S")
stream_formatter = logging.Formatter("%(message)s")
file_handler.setFormatter(file_formatter)
stream_handler.setFormatter(stream_formatter)
@@ -68,7 +74,7 @@ def __init__(self, config: dict):
if self.WRITE_TO_FILE:
self.__file_log.addHandler(file_handler)
self.__log.addHandler(stream_handler)
-
+
@staticmethod
def log_to_file(log_level: int, msg: str):
"""
@@ -76,7 +82,7 @@ def log_to_file(log_level: int, msg: str):
"""
if Logger.log is not None:
Logger.logger.log_this_file(log_level, msg)
-
+
@staticmethod
def log(log_level: int, msg: str): # for ease of access from outside
"""
@@ -94,7 +100,7 @@ def log_this_file(self, log_level: int, msg: str):
msg = msg.replace("\033[1;31m", "")
msg = msg.replace("\033[0;0m", "")
self.__file_log.log(log_level, msg)
-
+
def log_this(self, log_level: int, msg: str):
self.__log.log(log_level, msg)
if self.WRITE_TO_FILE:
diff --git a/mudpi.config.example b/mudpi.config.example
index 961e877..7978fee 100644
--- a/mudpi.config.example
+++ b/mudpi.config.example
@@ -16,27 +16,27 @@
},
"relays": [
{
- "pin": 13,
+ "pin": "D13",
"normally_open": true,
"group": "1",
"name": "Relay Name",
- "topic": "garden/pi/relays/1",
+ "topic": "garden/linux/relays/1",
"key": "relay_1"
},
{
- "pin": 12,
+ "pin": "D12",
"normally_open": true,
"group": "2",
"name": "Relay Name 2",
- "topic": "garden/pi/relays/2",
+ "topic": "garden/linux/relays/2",
"key": "relay_2"
},
{
- "pin": 20,
+ "pin": "D20",
"normally_open": true,
"group": "2",
"name": "Relay Name 3",
- "topic": "garden/pi/relays/3",
+ "topic": "garden/linux/relays/3",
"key": "relay_3"
}
],
@@ -47,11 +47,11 @@
"channel":"controls",
"controls": [
{
- "pin":18,
+ "pin":"D18",
"type":"button",
"name":"Button 1",
"key": "button_1",
- "topic": "garden/pi/relays/1",
+ "topic": "garden/linux/relays/1",
"action":"Toggle",
"resistor":"up",
"edge_detection":"falling",
@@ -68,7 +68,7 @@
"use_wifi":true,
"sensors": [
{
- "pin": 2,
+ "pin": "D2",
"is_digital": false,
"type": "Humidity",
"name": "Weather"
@@ -76,18 +76,18 @@
],
"controls": [
{
- "pin":16,
+ "pin": 16,
"type":"button",
"name":"Button 1",
"key": "button_1",
- "topic": "garden/pi/relays/1"
+ "topic": "garden/linux/relays/1"
},
{
"pin":5,
"type":"switch",
"name":"Switch 1",
"key": "switch_1",
- "topic": "garden/pi/relays/1"
+ "topic": "garden/linux/relays/1"
},
{
"pin":0,
@@ -153,7 +153,7 @@
"name": "Turn on Lights",
"key": "turn_on_lights_1",
"action": {"event":"Toggle"},
- "topic": "garden/pi/relays/2"
+ "topic": "garden/linux/relays/2"
}
]
}
diff --git a/mudpi.py b/mudpi.py
old mode 100755
new mode 100644
index 14ef29d..bc28e4c
--- a/mudpi.py
+++ b/mudpi.py
@@ -1,45 +1,56 @@
-import RPi.GPIO as GPIO
-import threading
+"""MudPi Core
+Author: Eric Davisson (@theDavisson) [EricDavisson.com]
+https://mudpi.app
+MudPi Core is a python library to gather sensor readings,
+control components,
+and manage devices using a Raspberry Pi on an event based system
+using redis.
+"""
+
import datetime
+import json
import socket
-import redis
+import threading
import time
-import json
-import sys
-sys.path.append('..')
+
+from adafruit_platformdetect import Detector
+
+
+import redis
+
+import constants
from action import Action
-from config_load import loadConfigJson
from server.mudpi_server import MudpiServer
-from workers.pi.lcd_worker import LcdWorker
-from workers.pi.i2c_worker import PiI2CWorker
-from workers.pi.relay_worker import RelayWorker
-from workers.pi.camera_worker import CameraWorker
-from workers.pi.sensor_worker import PiSensorWorker
-from workers.pi.control_worker import PiControlWorker
-from workers.trigger_worker import TriggerWorker
+from utils import load_config_json
+from workers.linux.control_worker import PiControlWorker
+from workers.linux.i2c_worker import PiI2CWorker
+from workers.linux.lcd_worker import LcdWorker
+from workers.linux.relay_worker import RelayWorker
+from workers.linux.sensor_worker import PiSensorWorker
from workers.sequence_worker import SequenceWorker
+from workers.trigger_worker import TriggerWorker
+
try:
- from workers.arduino.arduino_worker import ArduinoWorker
- NANPY_ENABLED = True
+ from workers.arduino.arduino_worker import ArduinoWorker
+
+ NANPY_ENABLED = True
except ImportError:
- NANPY_ENABLED = False
+ NANPY_ENABLED = False
+
try:
- from workers.adc_worker import ADCMCP3008Worker
- MCP_ENABLED = True
-except ImportError:
- MCP_ENABLED = False
+ from workers.adc_worker import ADCMCP3008Worker
+
+ MCP_ENABLED = True
+except (ImportError, AttributeError):
+ MCP_ENABLED = False
from logger.Logger import Logger, LOG_LEVEL
-import variables
-
-##############################
-# MudPi Core
-# Author: Eric Davisson (@theDavisson) [EricDavisson.com]
-# https://mudpi.app
-# MudPi Core is a python library to gather sensor readings, control components,
-# and manage devices using a Raspberry Pi on an event based system using redis.
-#
-CONFIGS = {}
+
+detector = Detector()
+if detector.board.any_raspberry_pi:
+ from workers.linux.camera_worker import CameraWorker
+
+
PROGRAM_RUNNING = True
threads = []
actions = {}
@@ -54,24 +65,34 @@
print(chr(27) + "[2J")
print('Loading MudPi Configs...\r', end="", flush=True)
-CONFIGS = loadConfigJson()
+
+try:
+ CONFIGS = load_config_json()
+except FileNotFoundError:
+ print(
+ f'{constants.RED_BACK}There is no configuration file present on the '
+ f'filesystem{constants.RESET}\n\r'
+ )
+ exit(1)
+
# Singleton redis to prevent connection conflicts
try:
- r = redis.Redis(host=CONFIGS['redis'].get('host', '127.0.0.1'), port=int(CONFIGS['redis'].get('port', 6379)))
+ r = redis.Redis(host=CONFIGS['redis'].get('host', '127.0.0.1'),
+ port=int(CONFIGS['redis'].get('port', 6379)))
except KeyError:
- r = redis.Redis(host='127.0.0.1', port=6379)
+ r = redis.Redis(host='127.0.0.1', port=6379)
# Waiting for redis and services to be running
-time.sleep(5)
-print('Loading MudPi Configs...\t\033[1;32m Complete\033[0;0m')
+time.sleep(5)
+print('Loading MudPi Configs...\t\033[1;32m Complete\033[0;0m')
print(chr(27) + "[2J")
# Print a display logo for startup
print("\033[1;32m")
-print(' __ __ _ _____ _ ')
-print('| \/ | | | __ (_)')
-print('| \ / |_ _ __| | |__) | ')
-print('| |\/| | | | |/ _` | ___/ | ')
-print('| | | | |_| | (_| | | | | ')
-print('|_| |_|\__,_|\__,_|_| |_| ')
+print(r' __ __ _ _____ _ ')
+print(r'| \/ | | | __ (_)')
+print(r'| \ / |_ _ __| | |__) | ')
+print(r'| |\/| | | | |/ _` | ___/ | ')
+print(r'| | | | |_| | (_| | | | | ')
+print(r'|_| |_|\__,_|\__,_|_| |_| ')
print('_________________________________________________')
print('')
print('Eric Davisson @theDavisson')
@@ -80,233 +101,390 @@
print('\033[0;0m')
if CONFIGS['debug'] is True:
- print('\033[1;33mDEBUG MODE ENABLED\033[0;0m')
- print("Loaded Config\n--------------------")
- for index, config in CONFIGS.items():
- if config != '':
- print('%s: %s' % (index, config))
- time.sleep(10)
+ print('\033[1;33mDEBUG MODE ENABLED\033[0;0m')
+ print("Loaded Config\n--------------------")
+ for index, config in CONFIGS.items():
+ if config != '':
+ print('%s: %s' % (index, config))
+ time.sleep(10)
try:
- print('Initializing Logger \r', end="", flush=True)
- Logger.logger = Logger(CONFIGS)
- time.sleep(0.05)
- Logger.log(LOG_LEVEL["info"], 'Initializing Logger...\t\t\t\033[1;32m Complete\033[0;0m')
- Logger.log_to_file(LOG_LEVEL["debug"], "Dumping the config file: ")
- for index, config in CONFIGS.items():
- Logger.log_to_file(LOG_LEVEL["debug"], '%s: %s' % (index, config))
- Logger.log_to_file(LOG_LEVEL["debug"], "End of config file dump!\n")
+ print('Initializing Logger \r', end="", flush=True)
+ Logger.logger = Logger(CONFIGS)
+ time.sleep(0.05)
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Initializing Logger...\t\t\t\033[1;32m Complete\033[0;0m'
+ )
+ Logger.log_to_file(LOG_LEVEL["debug"], "Dumping the config file: ")
+ for index, config in CONFIGS.items():
+ Logger.log_to_file(LOG_LEVEL["debug"], '%s: %s' % (index, config))
+ Logger.log_to_file(LOG_LEVEL["debug"], "End of config file dump!\n")
except Exception as e:
- Logger.log(LOG_LEVEL["info"], 'Initializing Logger...\t\t\t\033[1;31m Disabled\033[0;0m')
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Initializing Logger...\t\t\t\033[1;31m Disabled\033[0;0m'
+ )
try:
- print('Initializing Garden Control \r', end="", flush=True)
- GPIO.setwarnings(False)
- GPIO.setmode(GPIO.BCM)
- GPIO.cleanup()
- # Pause for GPIO to finish
- time.sleep(0.1)
- Logger.log(LOG_LEVEL["info"], 'Initializing Garden Control...\t\t\033[1;32m Complete\033[0;0m')
- print('Preparing Threads for Workers\r', end="", flush=True)
-
- new_messages_waiting = threading.Event() # Event to signal LCD to pull new messages
- main_thread_running = threading.Event() # Event to signal workers to close
- system_ready = threading.Event() # Event to tell workers to begin working
- camera_available = threading.Event() # Event to signal if camera can be used
- lcd_available = threading.Event() # Event to signal if lcd displays can be used
-
- main_thread_running.set() # Main event to tell workers to run/shutdown
- time.sleep(0.1)
- Logger.log(LOG_LEVEL["info"], 'Preparing Threads for Workers...\t\033[1;32m Complete\033[0;0m')
-
- # Worker for Camera
- try:
- if len(CONFIGS["camera"]) > 0:
- CONFIGS["camera"]["redis"] = r
- c = CameraWorker(CONFIGS['camera'], main_thread_running, system_ready, camera_available)
- Logger.log(LOG_LEVEL["info"], 'Camera...\t\t\t\033[1;32m Initializing\033[0;0m')
- workers.append(c)
- camera_available.set()
- except KeyError:
- Logger.log(LOG_LEVEL["info"], 'Pi Camera...\t\t\t\t\033[1;31m Disabled\033[0;0m')
-
- # Workers for pi (Sensors, Controls, Relays, I2C)
- try:
- if len(CONFIGS["workers"]) > 0:
- for worker in CONFIGS['workers']:
- # Create worker for worker
- worker["redis"] = r
- if worker['type'] == "sensor":
- pw = PiSensorWorker(worker, main_thread_running, system_ready)
- Logger.log(LOG_LEVEL["info"], 'Sensors...\t\t\t\t\033[1;32m Initializing\033[0;0m')
- elif worker['type'] == "control":
- pw = PiControlWorker(worker, main_thread_running, system_ready)
- Logger.log(LOG_LEVEL["info"], 'Controls...\t\t\t\t\033[1;32m Initializing\033[0;0m')
- elif worker['type'] == "i2c":
- pw = PiI2CWorker(worker, main_thread_running, system_ready)
- Logger.log(LOG_LEVEL["info"], 'I2C Comms...\t\t\t\t\033[1;32m Initializing\033[0;0m')
- elif worker['type'] == "display":
- for display in worker['displays']:
- display["redis"] = r
- pw = LcdWorker(display, main_thread_running, system_ready, lcd_available)
- lcd_available.set()
- Logger.log(LOG_LEVEL["info"], 'LCD Displays...\t\t\t\t\033[1;32m Initializing\033[0;0m')
- elif worker['type'] == "relay":
- # Add Relay Worker Here for Better Config Control
- Logger.log(LOG_LEVEL["info"], 'Relay...\t\t\t\033[1;32m Initializing\033[0;0m')
- else:
- Logger.log(LOG_LEVEL["warning"], "Exception raised due to unknown Worker Type: {0}".format(worker['type']))
- raise Exception("Unknown Worker Type: " + worker['type'])
- workers.append(pw)
- except KeyError as e:
- Logger.log(LOG_LEVEL["info"], 'Pi Workers...\t\t\t\t\033[1;31m Disabled\033[0;0m')
- print(e)
-
- # Worker for relays attached to pi
- try:
- if len(CONFIGS["relays"]) > 0:
- for relay in CONFIGS['relays']:
- relay["redis"] = r
- relayState = {
- "available": threading.Event(), # Event to allow relay to activate
- "active": threading.Event() # Event to signal relay to open/close
- }
- relayEvents[relay.get("key", relay_index)] = relayState
- rw = RelayWorker(relay, main_thread_running, system_ready, relayState['available'], relayState['active'])
- workers.append(rw)
- # Make the relays available, this event is toggled off elsewhere if we need to disable relays
- relayState['available'].set()
- relay_index +=1
- except KeyError:
- Logger.log(LOG_LEVEL["info"], 'Relays Workers...\t\t\033[1;31m Disabled\033[0;0m')
-
- # Load in Actions
- try:
- if len(CONFIGS["actions"]) > 0:
- for action in CONFIGS["actions"]:
- action["redis"] = r
- a = Action(action)
- a.init_action()
- actions[a.key] = a
- Logger.log(LOG_LEVEL["info"], '{0} Actions...\t\t\t\t\033[1;32m Initializing\033[0;0m'.format(len(CONFIGS['actions'])))
- except KeyError:
- Logger.log(LOG_LEVEL["info"], 'Actions...\t\t\t\033[1;31m Disabled\033[0;0m')
-
- # Worker for Sequences
- try:
- if len(CONFIGS["sequences"]) > 0:
- for sequence in CONFIGS["sequences"]:
- sequence["redis"] = r
- sequenceState = {
- "available": threading.Event(), # Event to allow sequence to activate
- "active": threading.Event() # Event to signal sequence to open/close
- }
- sequenceEvents[sequence.get("key", sequence_index)] = sequenceState
- s = SequenceWorker(sequence, main_thread_running, system_ready, sequenceState['available'], sequenceState['active'], actions)
- workers.append(s)
- sequences[s.key] = s
- sequenceState['available'].set()
- sequence_index +=1
- Logger.log(LOG_LEVEL["info"], '{0} Sequences...\t\t\t\t\033[1;32m Initializing\033[0;0m'.format(len(CONFIGS["sequences"])))
- except KeyError:
- Logger.log(LOG_LEVEL["info"], 'Sequences...\t\t\t\t\033[1;31m Disabled\033[0;0m')
-
- # Worker for Triggers
- try:
- if len(CONFIGS["triggers"]) > 0:
- for trigger in CONFIGS["triggers"]:
- trigger["redis"] = r
- t = TriggerWorker(CONFIGS['triggers'], main_thread_running, system_ready, actions, sequences)
- Logger.log(LOG_LEVEL["info"], 'Triggers...\t\t\t\t\033[1;32m Initializing\033[0;0m')
- workers.append(t)
- except KeyError:
- Logger.log(LOG_LEVEL["info"], 'Triggers...\t\t\t\t\033[1;31m Disabled\033[0;0m')
-
-
- # Worker for nodes attached to pi via serial or wifi[esp8266, esp32]
- # Supported nodes: arduinos, esp8266, ADC-MCP3xxx, probably others (esp32 with custom nanpy fork)
- try:
- if len(CONFIGS["nodes"]) > 0:
- for node in CONFIGS['nodes']:
- node["redis"] = r
- if node['type'] == "arduino":
- if NANPY_ENABLED:
- Logger.log(LOG_LEVEL["info"], 'MudPi Arduino Workers...\t\t\033[1;32m Initializing\033[0;0m')
- t = ArduinoWorker(node, main_thread_running, system_ready)
- else:
- Logger.log(LOG_LEVEL["error"], 'Error Loading Nanpy library. Did you pip3 install -r requirements.txt?')
- elif node['type'] == "ADC-MCP3008":
- if MCP_ENABLED:
- Logger.log(LOG_LEVEL["info"], 'MudPi ADC Workers...\t\t\033[1;32m Initializing\033[0;0m')
- t = ADCMCP3008Worker(node, main_thread_running, system_ready)
- else:
- Logger.log(LOG_LEVEL["error"], 'Error Loading MCP3xxx library. Did you pip3 install -r requirements.txt;?')
- else:
- Logger.log(LOG_LEVEL["warning"], "Exception raised due to unknown Node Type: {0}".format(node['type']))
- raise Exception("Unknown Node Type: " + node['type'])
- nodes.append(t)
- except KeyError as e:
- Logger.log(LOG_LEVEL["info"], 'MudPi Node Workers...\t\t\t\033[1;31m Disabled\033[0;0m')
-
- # try:
- # if (CONFIGS['server'] is not None):
- # Logger.log(LOG_LEVEL["info"], 'MudPi Server...\t\t\t\t\033[1;33m Starting\033[0;0m', end='\r', flush=True)
- # time.sleep(1)
- # server = MudpiServer(main_thread_running, CONFIGS['server']['host'], CONFIGS['server']['port'])
- # s = threading.Thread(target=server_worker) # TODO where is server_worker supposed to be initialized?
- # threads.append(s)
- # s.start()
- # except KeyError:
- # Logger.log(LOG_LEVEL["info"], 'MudPi Socket Server...\t\t\t\033[1;31m Disabled\033[0;0m')
-
- Logger.log(LOG_LEVEL["info"], 'MudPi Garden Controls...\t\t\033[1;32m Initialized\033[0;0m')
- Logger.log(LOG_LEVEL["info"], 'Engaging MudPi Workers...\t\t\033[1;32m \033[0;0m')
-
- for worker in workers:
- t = worker.run()
- threads.append(t)
- time.sleep(.5)
- for node in nodes:
- t = node.run()
- threads.append(t)
- time.sleep(.5)
-
- time.sleep(.5)
-
- Logger.log(LOG_LEVEL["info"], 'MudPi Garden Control...\t\t\t\033[1;32m Online\033[0;0m')
- Logger.log(LOG_LEVEL["info"], '_________________________________________________')
- system_ready.set() # Workers will not process until system is ready
- r.set('started_at', str(datetime.datetime.now())) # Store current time to track uptime
- system_message = {'event':'SystemStarted', 'data':1}
- r.publish('mudpi', json.dumps(system_message))
-
- # Hold the program here until its time to graceful shutdown
- while PROGRAM_RUNNING:
- # Main program loop
- # add logging or other system operations here...
- time.sleep(0.1)
+ print('Initializing Garden Control \r', end="", flush=True)
+ time.sleep(0.1)
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Initializing Garden Control...\t\t\033[1;32m Complete\033[0;0m'
+ )
+ print('Preparing Threads for Workers\r', end="", flush=True)
+
+ # Event to signal LCD to pull new messages
+ new_messages_waiting = threading.Event()
+ # Event to signal workers to close
+ main_thread_running = threading.Event()
+ # Event to tell workers to begin working
+ system_ready = threading.Event()
+ # Event to signal if camera can be used
+ camera_available = threading.Event()
+ # Event to signal if lcd displays can be used
+ lcd_available = threading.Event()
+
+ # Main event to tell workers to run/shutdown
+ main_thread_running.set()
+ time.sleep(0.1)
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Preparing Threads for Workers...\t\033[1;32m Complete\033[0;0m'
+ )
+
+ # Worker for Camera
+ try:
+ if len(CONFIGS["camera"]) > 0:
+ CONFIGS["camera"]["redis"] = r
+ c = CameraWorker(
+ CONFIGS['camera'],
+ main_thread_running,
+ system_ready,
+ camera_available
+ )
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Camera...\t\t\t\033[1;32m Initializing\033[0;0m'
+ )
+ workers.append(c)
+ camera_available.set()
+ except KeyError:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Pi Camera...\t\t\t\t\033[1;31m Disabled\033[0;0m'
+ )
+
+ # Workers for board (Sensors, Controls, Relays, I2C)
+ try:
+ if len(CONFIGS["workers"]) > 0:
+
+ for worker in CONFIGS['workers']:
+ # Create worker for worker
+ worker["redis"] = r
+
+ if worker['type'] == "sensor":
+ pw = PiSensorWorker(
+ worker,
+ main_thread_running,
+ system_ready
+ )
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sensors...\t\t\t\t\033[1;32m Initializing\033[0;0m'
+ )
+
+ elif worker['type'] == "control":
+ pw = PiControlWorker(
+ worker,
+ main_thread_running,
+ system_ready
+ )
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Controls...\t\t\t\t\033[1;32m Initializing\033[0;0m'
+ )
+
+ elif worker['type'] == "i2c":
+ pw = PiI2CWorker(worker, main_thread_running, system_ready)
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'I2C Comms...\t\t\t\t\033[1;32m Initializing\033[0;0m'
+ )
+
+ elif worker['type'] == "display":
+ for display in worker['displays']:
+ display["redis"] = r
+ pw = LcdWorker(
+ display,
+ main_thread_running,
+ system_ready,
+ lcd_available
+ )
+ lcd_available.set()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'LCD Displays...\t\t\t\t\033[1;32m Initializing\033[0;0m'
+ )
+
+ elif worker['type'] == "relay":
+ # Add Relay Worker Here for Better Config Control
+ Logger.log(LOG_LEVEL["info"],
+ 'Relay...\t\t\t\033[1;32m Initializing\033[0;0m')
+
+ else:
+ Logger.log(
+ LOG_LEVEL["warning"],
+ "Exception raised due to unknown Worker Type: {0}".format(
+ worker['type']))
+ raise Exception("Unknown Worker Type: " + worker['type'])
+ workers.append(pw)
+
+ except KeyError as e:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Pi Workers...\t\t\t\t\033[1;31m Disabled\033[0;0m'
+ )
+ print(e)
+
+ # Worker for relays attached to board
+ try:
+ if len(CONFIGS["relays"]) > 0:
+ for relay in CONFIGS['relays']:
+ relay["redis"] = r
+
+ relayState = {
+ # Event to allow relay to activate
+ "available": threading.Event(),
+ # Event to signal relay to open/close
+ "active": threading.Event()
+ }
+ relayEvents[relay.get("key", relay_index)] = relayState
+ rw = RelayWorker(
+ relay,
+ main_thread_running,
+ system_ready,
+ relayState['available'],
+ relayState['active']
+ )
+ workers.append(rw)
+ # Make the relays available, this event is toggled off
+ # elsewhere if we need to disable relays
+ relayState['available'].set()
+ relay_index += 1
+ except KeyError:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Relays Workers...\t\t\033[1;31m Disabled\033[0;0m'
+ )
+
+ # Load in Actions
+ try:
+ if len(CONFIGS["actions"]) > 0:
+ for action in CONFIGS["actions"]:
+ action["redis"] = r
+ a = Action(action)
+ a.init_action()
+ actions[a.key] = a
+ Logger.log(
+ LOG_LEVEL["info"],
+ '{0} Actions...\t\t\t\t\033[1;32m Initializing\033[0;0m'.format(
+ len(CONFIGS['actions']))
+ )
+ except KeyError:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Actions...\t\t\t\t\033[1;31m Disabled\033[0;0m'
+ )
+
+ # Worker for Sequences
+ try:
+ if len(CONFIGS["sequences"]) > 0:
+ for sequence in CONFIGS["sequences"]:
+ sequence["redis"] = r
+ sequenceState = {
+ "available": threading.Event(),
+ # Event to allow sequence to activate
+ "active": threading.Event()
+ # Event to signal sequence to open/close
+ }
+ sequenceEvents[
+ sequence.get("key", sequence_index)] = sequenceState
+ s = SequenceWorker(
+ sequence,
+ main_thread_running,
+ system_ready,
+ sequenceState['available'],
+ sequenceState['active'],
+ actions
+ )
+ workers.append(s)
+ sequences[s.key] = s
+ sequenceState['available'].set()
+ sequence_index += 1
+ Logger.log(
+ LOG_LEVEL["info"],
+ '{0} Sequences...\t\t\t\t\033[1;32m Initializing\033[0;0m'.format(
+ len(CONFIGS["sequences"]))
+ )
+ except KeyError:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequences...\t\t\t\t\033[1;31m Disabled\033[0;0m'
+ )
+
+ # Worker for Triggers
+ try:
+ if len(CONFIGS["triggers"]) > 0:
+ for trigger in CONFIGS["triggers"]:
+ trigger["redis"] = r
+ t = TriggerWorker(
+ CONFIGS['triggers'],
+ main_thread_running,
+ system_ready,
+ actions,
+ sequences
+ )
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Triggers...\t\t\t\t\033[1;32m Initializing\033[0;0m'
+ )
+ workers.append(t)
+ except KeyError:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Triggers...\t\t\t\t\033[1;31m Disabled\033[0;0m'
+ )
+
+ # Worker for nodes attached to board via serial or wifi[esp8266, esp32]
+ # Supported nodes: arduinos, esp8266, ADC-mcp3xxx, probably others
+ # (esp32 with custom nanpy fork)
+ try:
+ if len(CONFIGS["nodes"]) > 0:
+ for node in CONFIGS['nodes']:
+ node["redis"] = r
+
+ if node['type'] == "arduino":
+ if NANPY_ENABLED:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'MudPi Arduino Workers...\t\t\033[1;32m Initializing\033[0;0m'
+ )
+ t = ArduinoWorker(node, main_thread_running,
+ system_ready)
+ else:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'Error Loading Nanpy library. Did you pip3 install -r requirements.txt?'
+ )
+
+ elif node['type'] == "ADC-MCP3008":
+ if MCP_ENABLED:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'MudPi ADC Workers...\t\t\033[1;32m Initializing\033[0;0m'
+ )
+ t = ADCMCP3008Worker(node, main_thread_running,
+ system_ready)
+ else:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'Error Loading mcp3xxx library. Did you pip3 install -r requirements.txt;?'
+ )
+
+ else:
+ Logger.log(
+ LOG_LEVEL["warning"],
+ "Exception raised due to unknown Node Type: {0}".format(
+ node['type'])
+ )
+ raise Exception("Unknown Node Type: " + node['type'])
+ nodes.append(t)
+ except KeyError as e:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'MudPi Node Workers...\t\t\t\033[1;31m Disabled\033[0;0m'
+ )
+
+ # try:
+ # if (CONFIGS['server'] is not None):
+ # Logger.log(LOG_LEVEL["info"], 'MudPi Server...\t\t\t\t\033[1;33m Starting\033[0;0m', end='\r', flush=True)
+ # time.sleep(1)
+ # server = MudpiServer(main_thread_running, CONFIGS['server']['host'], CONFIGS['server']['port'])
+ # s = threading.Thread(target=server_worker) # TODO where is server_worker supposed to be initialized?
+ # threads.append(s)
+ # s.start()
+ # except KeyError:
+ # Logger.log(LOG_LEVEL["info"], 'MudPi Socket Server...\t\t\t\033[1;31m Disabled\033[0;0m')
+
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'MudPi Garden Controls...\t\t\033[1;32m Initialized\033[0;0m'
+ )
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Engaging MudPi Workers...\t\t\033[1;32m \033[0;0m'
+ )
+
+ for worker in workers:
+ t = worker.run()
+ threads.append(t)
+ time.sleep(.5)
+ for node in nodes:
+ t = node.run()
+ threads.append(t)
+ time.sleep(.5)
+
+ time.sleep(.5)
+
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'MudPi Garden Control...\t\t\t\033[1;32m Online\033[0;0m'
+ )
+ Logger.log(
+ LOG_LEVEL["info"],
+ '_________________________________________________'
+ )
+ # Workers will not process until system is ready
+ system_ready.set()
+ # Store current time to track uptime
+ r.set('started_at', str(datetime.datetime.now()))
+ system_message = {'event': 'SystemStarted', 'data': 1}
+ r.publish('mudpi', json.dumps(system_message))
+
+ # Hold the program here until its time to graceful shutdown
+ while PROGRAM_RUNNING:
+ # Main program loop
+ # add logging or other system operations here...
+ time.sleep(0.1)
except KeyboardInterrupt:
- PROGRAM_RUNNING = False
+ PROGRAM_RUNNING = False
finally:
- Logger.log(LOG_LEVEL["info"], 'MudPi Shutting Down...')
- # Perform any cleanup tasks here...
-
- try:
- server.sock.shutdown(socket.SHUT_RDWR)
- except:
- pass
-
- # Clear main running event to signal threads to close
- main_thread_running.clear()
-
- # Shutdown the camera loop
- camera_available.clear()
-
- # Join all our threads for shutdown
- for thread in threads:
- thread.join()
-
- Logger.log(LOG_LEVEL["info"], "MudPi Shutting Down...\t\t\t\033[1;32m Complete\033[0;0m")
- Logger.log(LOG_LEVEL["info"], "Mudpi is Now...\t\t\t\t\033[1;31m Offline\033[0;0m")
-
+ Logger.log(LOG_LEVEL["info"], 'MudPi Shutting Down...')
+ # Perform any cleanup tasks here...
+
+ try:
+ server.sock.shutdown(socket.SHUT_RDWR)
+ except Exception:
+ pass
+
+ # Clear main running event to signal threads to close
+ main_thread_running.clear()
+
+ # Shutdown the camera loop
+ camera_available.clear()
+
+ # Join all our threads for shutdown
+ for thread in threads:
+ thread.join()
+
+ Logger.log(
+ LOG_LEVEL["info"],
+ "MudPi Shutting Down...\t\t\t\033[1;32m Complete\033[0;0m"
+ )
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Mudpi is Now...\t\t\t\t\033[1;31m Offline\033[0;0m"
+ )
diff --git a/requirements.txt b/requirements.txt
index bdaeea1..a150cb9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,10 @@
pycron
redis
picamera
-Adafruit_DHT
+Adafruit-Blinka
+adafruit-circuitpython-dht
adafruit-circuitpython-mcp3xxx
-adafruit-blinka
adafruit-circuitpython-bme680
+smbus
https://github.com/olixr/nanpy/archive/master.zip
-git+https://github.com/olixr/Adafruit_CircuitPython_CharLCD.git#egg=adafruit-circuitpython-charlcd
\ No newline at end of file
+git+https://github.com/olixr/Adafruit_CircuitPython_CharLCD.git#egg=adafruit-circuitpython-charlcd
diff --git a/sensors/MCP3xxx/sensor.py b/sensors/MCP3xxx/sensor.py
deleted file mode 100644
index de4ba77..0000000
--- a/sensors/MCP3xxx/sensor.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import adafruit_mcp3xxx.mcp3008 as MCP
-import redis
-
-
-# Base sensor class to extend all other mcp3xxx sensors from.
-class Sensor:
-
- PINS = {
- 0: MCP.P0,
- 1: MCP.P1,
- 2: MCP.P2,
- 3: MCP.P3,
- 4: MCP.P4,
- 5: MCP.P5,
- 6: MCP.P6,
- 7: MCP.P7,
- }
-
- def __init__(self, pin: int, mcp, name=None, key=None, redis_conn=None):
- self.pin = pin
- self.mcp = mcp
- self.topic = None
-
- if key is None:
- raise Exception('No "key" Found in Sensor Config')
- else:
- self.key = key.replace(" ", "_").lower()
-
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
-
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
-
- def init_sensor(self):
- """
- Initialize the sensor here (i.e. set pin mode, get addresses, etc)
- :return:
- """
- raise NotImplementedError
-
- def read(self):
- """
- Read the sensor(s), parse the data and store it in redis if redis is configured
- :return:
- """
- raise NotImplementedError
-
- def readRaw(self):
- """
- Read the sensor(s) but return the raw voltage, useful for debugging
- :return:
- """
- return self.topic.voltage
-
- def readPin(self):
- """ Read the pin from the MCP3xxx as unaltered digital value"""
- return self.topic.value
diff --git a/sensors/arduino/float_sensor.py b/sensors/arduino/float_sensor.py
index 86b15bb..5efa9e6 100644
--- a/sensors/arduino/float_sensor.py
+++ b/sensors/arduino/float_sensor.py
@@ -1,31 +1,25 @@
-import time
-import datetime
-import json
-import redis
-from .sensor import Sensor
-from nanpy import (ArduinoApi, SerialManager)
-import sys
-sys.path.append('..')
+from nanpy import (SerialManager)
-import variables
+from sensors.arduino.sensor import Sensor
default_connection = SerialManager(device='/dev/ttyUSB0')
-#r = redis.Redis(host='127.0.0.1', port=6379)
+
class FloatSensor(Sensor):
- def __init__(self, pin, name=None, key=None, connection=default_connection, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, redis_conn=redis_conn)
- return
+ def __init__(self, pin, name=None, key=None, connection=default_connection,
+ redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection,
+ redis_conn=redis_conn)
- def init_sensor(self):
- # read data using pin specified pin
- self.api.pinMode(self.pin, self.api.INPUT)
+ def init_sensor(self):
+ # read data using pin specified pin
+ self.api.pinMode(self.pin, self.api.INPUT)
- def read(self):
- value = self.api.digitalRead(self.pin)
- self.r.set(self.key, value)
- return value
+ def read(self):
+ value = self.api.digitalRead(self.pin)
+ self.r.set(self.key, value)
+ return value
- def readRaw(self):
- return self.read()
\ No newline at end of file
+ def read_raw(self):
+ return self.read()
diff --git a/sensors/arduino/humidity_sensor.py b/sensors/arduino/humidity_sensor.py
index 1126e6b..0d74b88 100644
--- a/sensors/arduino/humidity_sensor.py
+++ b/sensors/arduino/humidity_sensor.py
@@ -5,42 +5,48 @@
from .sensor import Sensor
from nanpy import (ArduinoApi, SerialManager, DHT)
import sys
-sys.path.append('..')
-import variables
+
+
+import constants
default_connection = SerialManager(device='/dev/ttyUSB0')
-#r = redis.Redis(host='127.0.0.1', port=6379)
+
+
+# r = redis.Redis(host='127.0.0.1', port=6379)
class HumiditySensor(Sensor):
- def __init__(self, pin, name=None, key=None, connection=default_connection, model='11', api=None, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, redis_conn=redis_conn)
- self.type = model #DHT11 or DHT22 maybe AM2302
- return
-
- def init_sensor(self):
- # prepare sensor on specified pin
- sensor_types = { '11': DHT.DHT11,
- '22': DHT.DHT22,
- '2301': DHT.AM2301 }
- if self.type in sensor_types:
- self.sensor = sensor_types[self.type]
- else:
- # print('Sensor Type Error: Defaulting to DHT11')
- self.sensor = DHT.DHT11
- self.dht = DHT(self.pin, self.sensor, connection=self.connection)
-
- def read(self):
- #Pass true to read in american degrees :)
- temperature = self.dht.readTemperature(True)
- humidity = self.dht.readHumidity()
- data = {'temperature': round(temperature, 2), 'humidity': round(humidity, 2)}
- self.r.set(self.key + '_temperature', temperature)
- self.r.set(self.key + '_humidity', humidity)
- self.r.set(self.key, json.dumps(data))
- return data
-
- def readRaw(self):
- return self.read()
\ No newline at end of file
+ def __init__(self, pin, name=None, key=None, connection=default_connection,
+ model='11', api=None, redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection,
+ redis_conn=redis_conn)
+ self.type = model # DHT11 or DHT22 maybe AM2302
+ return
+
+ def init_sensor(self):
+ # prepare sensor on specified pin
+ sensor_types = {'11': DHT.DHT11,
+ '22': DHT.DHT22,
+ '2301': DHT.AM2301}
+ if self.type in sensor_types:
+ self.sensor = sensor_types[self.type]
+ else:
+ # print('Sensor Type Error: Defaulting to DHT11')
+ self.sensor = DHT.DHT11
+ self.dht = DHT(self.pin, self.sensor, connection=self.connection)
+
+ def read(self):
+ # Pass true to read in american degrees :)
+ temperature = self.dht.readTemperature(True)
+ humidity = self.dht.readHumidity()
+ data = {'temperature': round(temperature, 2),
+ 'humidity': round(humidity, 2)}
+ self.r.set(self.key + '_temperature', temperature)
+ self.r.set(self.key + '_humidity', humidity)
+ self.r.set(self.key, json.dumps(data))
+ return data
+
+ def read_raw(self):
+ return self.read()
diff --git a/sensors/arduino/light_sensor.py b/sensors/arduino/light_sensor.py
index deae097..90f35c8 100644
--- a/sensors/arduino/light_sensor.py
+++ b/sensors/arduino/light_sensor.py
@@ -5,27 +5,32 @@
from .sensor import Sensor
from nanpy import (ArduinoApi, SerialManager)
import sys
-sys.path.append('..')
-import variables
+
+
+import constants
default_connection = SerialManager(device='/dev/ttyUSB0')
-#r = redis.Redis(host='127.0.0.1', port=6379)
+
+
+# r = redis.Redis(host='127.0.0.1', port=6379)
class LightSensor(Sensor):
- def __init__(self, pin, name=None, key=None, connection=default_connection, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, redis_conn=redis_conn)
- return
+ def __init__(self, pin, name=None, key=None, connection=default_connection,
+ redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection,
+ redis_conn=redis_conn)
+ return
- def init_sensor(self):
- # read data using pin specified pin
- self.api.pinMode(self.pin, self.api.INPUT)
+ def init_sensor(self):
+ # read data using pin specified pin
+ self.api.pinMode(self.pin, self.api.INPUT)
- def read(self):
- light_intesity = self.api.analogRead(self.pin)
- self.r.set(self.key, light_intesity)
- return light_intesity
+ def read(self):
+ light_intesity = self.api.analogRead(self.pin)
+ self.r.set(self.key, light_intesity)
+ return light_intesity
- def readRaw(self):
- return self.read()
\ No newline at end of file
+ def read_raw(self):
+ return self.read()
diff --git a/sensors/arduino/rain_sensor.py b/sensors/arduino/rain_sensor.py
index dbdb3eb..984b88e 100644
--- a/sensors/arduino/rain_sensor.py
+++ b/sensors/arduino/rain_sensor.py
@@ -5,68 +5,74 @@
from .sensor import Sensor
from nanpy import (ArduinoApi, SerialManager)
import sys
-sys.path.append('..')
-import variables
+
+
+import constants
+
default_connection = SerialManager(device='/dev/ttyUSB0')
-#r = redis.Redis(host='127.0.0.1', port=6379)
+# r = redis.Redis(host='127.0.0.1', port=6379)
-#The resistor reads lower the more water on the sensor
-NO_RAIN_BOUNDS = 1000 # and above
+# The resistor reads lower the more water on the sensor
+NO_RAIN_BOUNDS = 1000 # and above
MIST_BOUNDS = 800
LIGHT_RAIN_BOUNDS = 750
RAIN_BOUNDS = 550
HEAVY_RAIN_BOUNDS = 400
-DOWNPOUR_BOUNDS = 300 # and below
+DOWNPOUR_BOUNDS = 300 # and below
+
class RainSensor(Sensor):
- def __init__(self, pin, name=None, key=None, connection=default_connection, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, redis_conn=redis_conn)
- return
+ def __init__(self, pin, name=None, key=None, connection=default_connection,
+ redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection,
+ redis_conn=redis_conn)
+ return
+
+ def init_sensor(self):
+ # read data using pin specified pin
+ self.api.pinMode(self.pin, self.api.INPUT)
- def init_sensor(self):
- # read data using pin specified pin
- self.api.pinMode(self.pin, self.api.INPUT)
+ def read(self):
+ rain = self.api.analogRead(self.pin) # TODO: REMOVE (PERSONAL CONFIG)
+ # rain = self.parseSensorReading(self.api.analogRead(self.pin))
+ self.r.set(self.key, rain)
+ return rain
- def read(self):
- rain = self.api.analogRead(self.pin) #TODO: REMOVE (PERSONAL CONFIG)
- #rain = self.parseSensorReading(self.api.analogRead(self.pin))
- self.r.set(self.key, rain)
- return rain
+ def read_raw(self):
+ resistance = self.api.analogRead(self.pin)
+ # print("Resistance: %d" % resistance)
+ self.r.set(self.key + '_raw', resistance)
+ return resistance
- def readRaw(self):
- resistance = self.api.analogRead(self.pin)
- #print("Resistance: %d" % resistance)
- self.r.set(self.key+'_raw', resistance)
- return resistance
+ def parseSensorReading(self, raw_data):
+ if (raw_data > MIST_BOUNDS):
+ return 'No Rain'
+ elif (raw_data <= MIST_BOUNDS and raw_data > LIGHT_RAIN_BOUNDS):
+ return 'Mist'
+ elif (raw_data <= LIGHT_RAIN_BOUNDS and raw_data > RAIN_BOUNDS):
+ return 'Light Rain'
+ elif (raw_data <= RAIN_BOUNDS and raw_data > HEAVY_RAIN_BOUNDS):
+ return 'Rain'
+ elif (raw_data <= HEAVY_RAIN_BOUNDS and raw_data > DOWNPOUR_BOUNDS):
+ return 'Heavy Rain'
+ elif (raw_data <= DOWNPOUR_BOUNDS):
+ return 'Downpour'
+ else:
+ return 'Bad Sensor Data'
- def parseSensorReading(self, raw_data):
- if(raw_data > MIST_BOUNDS):
- return 'No Rain'
- elif(raw_data <= MIST_BOUNDS and raw_data > LIGHT_RAIN_BOUNDS):
- return 'Mist'
- elif(raw_data <= LIGHT_RAIN_BOUNDS and raw_data > RAIN_BOUNDS):
- return 'Light Rain'
- elif(raw_data <= RAIN_BOUNDS and raw_data > HEAVY_RAIN_BOUNDS):
- return 'Rain'
- elif(raw_data <= HEAVY_RAIN_BOUNDS and raw_data > DOWNPOUR_BOUNDS):
- return 'Heavy Rain'
- elif(raw_data <= DOWNPOUR_BOUNDS):
- return 'Downpour'
- else:
- return 'Bad Sensor Data'
if __name__ == '__main__':
- try:
- loop_count = 10
- while (loop_count > 0):
- sensor = RainSensor(4)
- rainread = sensor.read()
- print('Rain: ', rainread)
- loop_count += 1
- time.sleep(1)
- except KeyboardInterrupt:
- pass
- finally:
- print('Rain Sensor Closing...')
\ No newline at end of file
+ try:
+ loop_count = 10
+ while (loop_count > 0):
+ sensor = RainSensor(4)
+ rainread = sensor.read()
+ print('Rain: ', rainread)
+ loop_count += 1
+ time.sleep(1)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ print('Rain Sensor Closing...')
diff --git a/sensors/arduino/sensor.py b/sensors/arduino/sensor.py
index 14244e3..60e61d4 100644
--- a/sensors/arduino/sensor.py
+++ b/sensors/arduino/sensor.py
@@ -2,45 +2,54 @@
import json
import redis
from nanpy import (ArduinoApi, SerialManager)
+from sensors.base_sensor import BaseSensor
default_connection = SerialManager()
-# Base sensor class to extend all other arduino sensors from.
-class Sensor():
-
- def __init__(self, pin, name=None, connection=default_connection, analog_pin_mode=False, key=None, api=None, redis_conn=None):
- self.pin = pin
- if key is None:
- raise Exception('No "key" Found in Sensor Config')
- else:
- self.key = key.replace(" ", "_").lower()
-
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
- self.analog_pin_mode = analog_pin_mode
- self.connection = connection
- self.api = api if api is not None else ArduinoApi(connection)
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- return
-
- def init_sensor(self):
- #Initialize the sensor here (i.e. set pin mode, get addresses, etc)
- pass
-
- def read(self):
- #Read the sensor(s), parse the data and store it in redis if redis is configured
- pass
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- pass
-
- def readPin(self):
- #Read the pin from the ardiuno. Can be analog or digital based on "analog_pin_mode"
- data = self.api.analogRead(self.pin) if analog_pin_mode else self.api.digitalRead(self.pin)
- return data
\ No newline at end of file
+
+class Sensor(BaseSensor):
+ """
+ Base sensor class to extend all other arduino sensors from.
+ """
+
+ def __init__(self, pin, name=None, connection=default_connection,
+ analog_pin_mode=False, key=None, api=None, redis_conn=None):
+ """
+
+ Args:
+ pin:
+ name:
+ connection:
+ analog_pin_mode:
+ key:
+ api:
+ redis_conn:
+ """
+
+ super().__init__(
+ pin=pin,
+ name=name,
+ key=key,
+ redis_conn=redis_conn
+ )
+
+ self.analog_pin_mode = analog_pin_mode
+ self.connection = connection
+ self.api = api if api is not None else ArduinoApi(connection)
+
+ def read_pin(self):
+ """
+ Read the pin from the ardiuno. Can be analog or digital based on
+ "analog_pin_mode"
+
+ Returns:
+
+ """
+
+ if self.analog_pin_mode:
+ data = self.api.analogRead(self.pin)
+
+ else:
+ data = self.api.digitalRead(self.pin)
+
+ return data
diff --git a/sensors/arduino/soil_sensor.py b/sensors/arduino/soil_sensor.py
index 0f29e1a..414ef88 100644
--- a/sensors/arduino/soil_sensor.py
+++ b/sensors/arduino/soil_sensor.py
@@ -5,46 +5,53 @@
from .sensor import Sensor
from nanpy import (ArduinoApi, SerialManager)
import sys
-sys.path.append('..')
-import variables
+
+
+import constants
default_connection = SerialManager(device='/dev/ttyUSB0')
-#r = redis.Redis(host='127.0.0.1', port=6379)
+# r = redis.Redis(host='127.0.0.1', port=6379)
# Wet Water = 287
# Dry Air = 584
AirBounds = 590;
WaterBounds = 280;
-intervals = int((AirBounds - WaterBounds)/3);
+intervals = int((AirBounds - WaterBounds) / 3);
+
+
class SoilSensor(Sensor):
- def __init__(self, pin, name=None, key=None, connection=default_connection, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, redis_conn=redis_conn)
- return
-
- def init_sensor(self):
- # read data using pin specified pin
- self.api.pinMode(self.pin, self.api.INPUT)
-
- def read(self):
- resistance = self.api.analogRead(self.pin)
- moistpercent = ((resistance - WaterBounds) / (AirBounds - WaterBounds)) * 100
- if(moistpercent > 80):
- moisture = 'Very Dry - ' + str(int(moistpercent))
- elif(moistpercent <= 80 and moistpercent > 45):
- moisture = 'Dry - ' + str(int(moistpercent))
- elif(moistpercent <= 45 and moistpercent > 25):
- moisture = 'Wet - ' + str(int(moistpercent))
- else:
- moisture = 'Very Wet - ' + str(int(moistpercent))
- #print("Resistance: %d" % resistance)
- #TODO: Put redis store into sensor worker
- self.r.set(self.key, resistance) #TODO: CHANGE BACK TO 'moistpercent' (PERSONAL CONFIG)
- return resistance
-
- def readRaw(self):
- resistance = self.api.analogRead(self.pin)
- #print("Resistance: %d" % resistance)
- self.r.set(self.key+'_raw', resistance)
- return resistance
\ No newline at end of file
+ def __init__(self, pin, name=None, key=None, connection=default_connection,
+ redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection,
+ redis_conn=redis_conn)
+ return
+
+ def init_sensor(self):
+ # read data using pin specified pin
+ self.api.pinMode(self.pin, self.api.INPUT)
+
+ def read(self):
+ resistance = self.api.analogRead(self.pin)
+ moistpercent = ((resistance - WaterBounds) / (
+ AirBounds - WaterBounds)) * 100
+ if (moistpercent > 80):
+ moisture = 'Very Dry - ' + str(int(moistpercent))
+ elif (moistpercent <= 80 and moistpercent > 45):
+ moisture = 'Dry - ' + str(int(moistpercent))
+ elif (moistpercent <= 45 and moistpercent > 25):
+ moisture = 'Wet - ' + str(int(moistpercent))
+ else:
+ moisture = 'Very Wet - ' + str(int(moistpercent))
+ # print("Resistance: %d" % resistance)
+ # TODO: Put redis store into sensor worker
+ self.r.set(self.key,
+ resistance) # TODO: CHANGE BACK TO 'moistpercent' (PERSONAL CONFIG)
+ return resistance
+
+ def read_raw(self):
+ resistance = self.api.analogRead(self.pin)
+ # print("Resistance: %d" % resistance)
+ self.r.set(self.key + '_raw', resistance)
+ return resistance
diff --git a/sensors/arduino/temperature_sensor.py b/sensors/arduino/temperature_sensor.py
index ff1c35e..077c40e 100644
--- a/sensors/arduino/temperature_sensor.py
+++ b/sensors/arduino/temperature_sensor.py
@@ -7,63 +7,70 @@
import sys
from logger.Logger import Logger, LOG_LEVEL
-sys.path.append('..')
-import variables
+
+
+import constants
default_connection = SerialManager(device='/dev/ttyUSB0')
-#r = redis.Redis(host='127.0.0.1', port=6379)
+
+
+# r = redis.Redis(host='127.0.0.1', port=6379)
class TemperatureSensor(Sensor):
- def __init__(self, pin, name=None, key=None, connection=default_connection, redis_conn=None):
- super().__init__(pin, name=name, key=key, connection=connection, redis_conn=redis_conn)
- return
-
- def init_sensor(self):
- self.sensors = DallasTemperature(self.pin, connection=self.connection)
- self.sensor_bus = self.sensors.getDeviceCount()
- # read data using pin specified pin
- Logger.log(LOG_LEVEL["debug"], "There are",self.sensor_bus, "devices connected on pin ", self.sensors.pin)
- self.addresses = []
-
- for i in range(self.sensor_bus):
- self.addresses.append(self.sensors.getAddress(i))
-
- Logger.log(LOG_LEVEL["debug"], "Their addresses", self.addresses)
- #I guess this is something with bit rates? TODO: Look this up
- self.sensors.setResolution(10)
-
- #sensor = id of sensor you want in addresses[]
- def read(self):
- #temp = self.sensors.getTempF(sensor)
- #self.r.set('temp_'+str(sensor), temp)
- #return temp
- return self.readAll()
-
- def readAll(self):
- self.sensors.requestTemperatures()
- temps = {}
- for i in range(self.sensor_bus):
- temp = self.sensors.getTempC(i)
- temps['temp_'+str(i)] = temp
- #self.r.set(self.key+'_'+str(i), temp)
- #print("Device %d (%s) " % (i, self.addresses[i]))
- #print("Let's convert it in Fahrenheit degrees: %0.2f" % DallasTemperature.toFahrenheit(temp))
- self.r.set(self.key, temps)
- return temps
+ def __init__(self, pin, name=None, key=None, connection=default_connection,
+ redis_conn=None):
+ super().__init__(pin, name=name, key=key, connection=connection,
+ redis_conn=redis_conn)
+ return
+
+ def init_sensor(self):
+ self.sensors = DallasTemperature(self.pin, connection=self.connection)
+ self.sensor_bus = self.sensors.getDeviceCount()
+ # read data using pin specified pin
+ Logger.log(LOG_LEVEL["debug"], "There are", self.sensor_bus,
+ "devices connected on pin ", self.sensors.pin)
+ self.addresses = []
+
+ for i in range(self.sensor_bus):
+ self.addresses.append(self.sensors.getAddress(i))
+
+ Logger.log(LOG_LEVEL["debug"], "Their addresses", self.addresses)
+ # I guess this is something with bit rates? TODO: Look this up
+ self.sensors.setResolution(10)
+
+ # sensor = id of sensor you want in addresses[]
+ def read(self):
+ # temp = self.sensors.getTempF(sensor)
+ # self.r.set('temp_'+str(sensor), temp)
+ # return temp
+ return self.readAll()
+
+ def readAll(self):
+ self.sensors.requestTemperatures()
+ temps = {}
+ for i in range(self.sensor_bus):
+ temp = self.sensors.getTempC(i)
+ temps['temp_' + str(i)] = temp
+ # self.r.set(self.key+'_'+str(i), temp)
+ # print("Device %d (%s) " % (i, self.addresses[i]))
+ # print("Let's convert it in Fahrenheit degrees: %0.2f" % DallasTemperature.toFahrenheit(temp))
+ self.r.set(self.key, temps)
+ return temps
+
if __name__ == '__main__':
- try:
- loop_count = 10
- sensor = TemperatureSensor(2)
- sensor.init_sensor()
- while (loop_count > 0):
- tempread = sensor.readAll()
- print('Temps: ', tempread)
- loop_count += 1
- time.sleep(2)
- except KeyboardInterrupt:
- pass
- finally:
- print('Temp Sensors Closing...')
\ No newline at end of file
+ try:
+ loop_count = 10
+ sensor = TemperatureSensor(2)
+ sensor.init_sensor()
+ while (loop_count > 0):
+ tempread = sensor.readAll()
+ print('Temps: ', tempread)
+ loop_count += 1
+ time.sleep(2)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ print('Temp Sensors Closing...')
diff --git a/sensors/base_sensor.py b/sensors/base_sensor.py
new file mode 100644
index 0000000..50d9cf5
--- /dev/null
+++ b/sensors/base_sensor.py
@@ -0,0 +1,35 @@
+import redis
+
+
+class BaseSensor:
+
+ def __init__(self, pin, name=None, key=None, redis_conn=None):
+ self.pin = pin
+
+ if key is None:
+ raise Exception('No "key" Found in Sensor Config')
+ else:
+ self.key = key.replace(" ", "_").lower()
+
+ if name is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = name
+
+ try:
+ self.r = redis_conn if redis_conn is not None else redis.Redis(
+ host='127.0.0.1', port=6379)
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+
+ def init_sensor(self):
+ pass
+
+ def read(self):
+ pass
+
+ def read_raw(self):
+ pass
+
+ def read_pin(self):
+ raise NotImplementedError
diff --git a/sensors/MCP3xxx/__init__.py b/sensors/linux/__init__.py
similarity index 100%
rename from sensors/MCP3xxx/__init__.py
rename to sensors/linux/__init__.py
diff --git a/sensors/linux/float_sensor.py b/sensors/linux/float_sensor.py
new file mode 100644
index 0000000..2323658
--- /dev/null
+++ b/sensors/linux/float_sensor.py
@@ -0,0 +1,32 @@
+import board
+import digitalio
+
+from .sensor import Sensor
+
+
+# PIN MODE : OUT | IN
+
+class FloatSensor(Sensor):
+
+ def __init__(self, pin, name=None, key=None, redis_conn=None):
+ super().__init__(pin, name=name, key=key, redis_conn=redis_conn)
+ self.pin_obj = getattr(board, pin)
+ return
+
+ def init_sensor(self):
+ """Initialize the sensor here (i.e. set pin mode, get addresses, etc)
+ this gets called by the worker"""
+ # Default to input :
+ # https://github.com/adafruit/Adafruit_Blinka/blob/master/src/digitalio.py#L111
+ self.gpio_pin = digitalio.DigitalInOut(self.pin_obj)
+ return
+
+ def read(self):
+ """Read the sensor(s), parse the data and store it in redis if redis
+ is configured"""
+ value = self.gpio_pin.value
+ return value
+
+ def read_raw(self):
+ """Read the sensor(s) but return the raw data, useful for debugging"""
+ return self.read()
diff --git a/sensors/linux/humidity_sensor.py b/sensors/linux/humidity_sensor.py
new file mode 100644
index 0000000..e8fa796
--- /dev/null
+++ b/sensors/linux/humidity_sensor.py
@@ -0,0 +1,100 @@
+import json
+import sys
+import time
+import board
+import adafruit_dht
+
+from sensors.linux.sensor import Sensor
+
+
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+# PIN MODE : OUT | IN
+
+
+class HumiditySensor(Sensor):
+
+ def __init__(self, pin, name=None, key=None, model='11', redis_conn=None):
+ super().__init__(pin, name=name, key=key, redis_conn=redis_conn)
+ self.pin_obj = getattr(board, pin)
+ self.type = model
+ return
+
+ def init_sensor(self):
+ """
+ Initialize the sensor here (i.e. set pin mode, get addresses, etc)
+ this gets called by the worker
+ """
+ sensor_types = {
+ '11': adafruit_dht.DHT11,
+ '22': adafruit_dht.DHT22,
+ '2302': adafruit_dht.DHT22
+ } # AM2302 = DHT22
+ if self.type in sensor_types:
+ self.sensor = sensor_types[self.type]
+ else:
+ Logger.log(
+ LOG_LEVEL["warning"],
+ 'Sensor Model Error: Defaulting to DHT11'
+ )
+ self.sensor = adafruit_dht.DHT11
+ return
+
+ def read(self):
+ """
+ Read the sensor(s), parse the data and store it in redis if redis
+ is configured
+ """
+ # Set values just in case we never set them up.
+
+ humidity = None
+ temperature_c = None
+
+ # read_retry() not implemented in new lib
+ for i in range(15):
+ dhtDevice = self.sensor(self.pin_obj)
+
+ try:
+ temperature_c = dhtDevice.temperature
+ humidity = dhtDevice.humidity
+ if humidity is not None and temperature_c is not None:
+ dhtDevice.exit()
+ break
+
+ except RuntimeError:
+ # Errors happen fairly often, DHT's are hard to read,
+ # just keep going:
+ time.sleep(2)
+ continue
+
+ if humidity is not None and temperature_c is not None:
+ self.r.set(
+ self.key + '_temperature',
+ round(temperature_c * 1.8 + 32, 2)
+ )
+ self.r.set(
+ self.key + '_humidity', humidity
+ )
+ readings = {
+ 'temperature': round(temperature_c * 1.8 + 32, 2),
+ 'humidity': round(humidity, 2)
+ }
+ self.r.set(self.key, json.dumps(readings))
+ return readings
+ else:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'DHT Reading was Invalid. Trying again next cycle.'
+ )
+ return None
+
+ def read_raw(self):
+ """
+ Read the sensor(s) but return the raw data, useful for debugging
+
+ Returns:
+
+ """
+ return self.read()
diff --git a/sensors/pi/__init__.py b/sensors/linux/i2c/__init__.py
similarity index 100%
rename from sensors/pi/__init__.py
rename to sensors/linux/i2c/__init__.py
diff --git a/sensors/linux/i2c/bme680_sensor.py b/sensors/linux/i2c/bme680_sensor.py
new file mode 100644
index 0000000..649762b
--- /dev/null
+++ b/sensors/linux/i2c/bme680_sensor.py
@@ -0,0 +1,57 @@
+import json
+import sys
+
+import adafruit_bme680
+
+from logger.Logger import Logger, LOG_LEVEL
+from sensors.linux.i2c.sensor import Sensor
+
+
+
+
+class Bme680Sensor(Sensor):
+
+ def __init__(self, address=None, name=None, key=None, redis_conn=None):
+ super().__init__(address, name=name, key=key, redis_conn=redis_conn)
+ return
+
+ def init_sensor(self):
+ self.sensor = adafruit_bme680.Adafruit_BME680_I2C(
+ self.i2c, debug=False
+ )
+ # change this to match the location's pressure (hPa) at sea level
+ self.sensor.sea_level_pressure = 1013.25
+ return
+
+ def read(self):
+ temperature = round((self.sensor.temperature - 5) * 1.8 + 32, 2)
+ gas = self.sensor.gas
+ humidity = round(self.sensor.humidity, 1)
+ pressure = round(self.sensor.pressure, 2)
+ altitude = round(self.sensor.altitude, 3)
+
+ if humidity is not None and temperature is not None:
+ self.r.set(self.key + '_temperature', temperature)
+ self.r.set(self.key + '_humidity', humidity)
+ self.r.set(self.key + '_gas', gas)
+ self.r.set(self.key + '_pressure', pressure)
+ self.r.set(self.key + '_altitude', altitude)
+ readings = {
+ 'temperature': temperature,
+ 'humidity': humidity,
+ 'pressure': pressure,
+ 'gas': gas,
+ 'altitude': altitude
+ }
+ self.r.set(self.key, json.dumps(readings))
+ # print('BME680:', readings)
+ return readings
+ else:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'Failed to get reading [BME680]. Try again!'
+ )
+
+ def read_raw(self):
+ """Read the sensor(s) but return the raw data, useful for debugging"""
+ return self.read()
diff --git a/sensors/linux/i2c/sensor.py b/sensors/linux/i2c/sensor.py
new file mode 100644
index 0000000..60d060e
--- /dev/null
+++ b/sensors/linux/i2c/sensor.py
@@ -0,0 +1,53 @@
+import time
+import json
+import redis
+import board
+from busio import I2C
+
+
+# PIN MODE : OUT | IN
+
+class Sensor():
+
+ def __init__(self, address, name=None, key=None, redis_conn=None):
+ self.address = address
+
+ if key is None:
+ raise Exception('No "key" Found in I2C Sensor Config')
+ else:
+ self.key = key.replace(" ", "_").lower()
+
+ if name is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = name
+
+ self.i2c = I2C(board.SCL, board.SDA)
+ try:
+ self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+ return
+
+ def init_sensor(self):
+ """Initialize the sensor here (i.e. set pin mode, get addresses, etc)"""
+ # GPIO.setmode(GPIO.BCM)
+ # GPIO.setup(pin, GPIO.IN)
+ pass
+
+ def read(self):
+ """Read the sensor(s), parse the data and store it in redis if redis is configured"""
+ # GPIO.input(pin)
+ pass
+
+ def read_raw(self):
+ """Read the sensor(s) but return the raw data, useful for debugging"""
+ pass
+
+ # self.pin not defined and read_pin() doesn't seemto be called. So I commented it
+ '''
+ def read_pin(self):
+ """Read the pin from the board. Can be analog or digital based on \"analog_pin_mode\""""
+ data = self.gpio.input(self.pin)
+ return data
+ '''
diff --git a/sensors/linux/i2c/t9602_sensor.py b/sensors/linux/i2c/t9602_sensor.py
new file mode 100644
index 0000000..5eab007
--- /dev/null
+++ b/sensors/linux/i2c/t9602_sensor.py
@@ -0,0 +1,53 @@
+import json
+import sys
+
+import smbus
+
+from logger.Logger import Logger, LOG_LEVEL
+from sensors.linux.i2c.sensor import Sensor
+
+
+
+
+class T9602Sensor(Sensor):
+
+ def __init__(self, address=None, name=None, key=None, redis_conn=None):
+ super().__init__(address, name=name, key=key, redis_conn=redis_conn)
+ self.address = address
+ return
+
+ def init_sensor(self):
+ '''This is the bus number : the 1 in "/dev/i2c-1"
+ I enforced it to 1 because there is only one on Raspberry Pi.
+ We might want to add this parameter in i2c sensor config in the future.
+ We might encounter boards with several buses.'''
+ self.bus = smbus.SMBus(1)
+ return
+
+ def read(self):
+ data = self.bus.read_i2c_block_data(self.address, 0, 4)
+
+ humidity = (((data[0] & 0x3F) << 8) + data[1]) / 16384.0 * 100.0
+ temperature_c = ((data[2] * 64) + (data[3] >> 2)) / 16384.0 * 165.0 - 40.0
+
+ humidity = round(humidity, 2)
+ temperature_c = round(temperature_c, 2)
+
+ if humidity is not None and temperature_c is not None:
+ self.r.set(self.key + '_temperature', temperature_c)
+ self.r.set(self.key + '_humidity', humidity)
+ readings = {
+ 'temperature': temperature_c,
+ 'humidity': humidity
+ }
+ self.r.set(self.key, json.dumps(readings))
+ return readings
+ else:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'Failed to get reading [t9602]. Try again!'
+ )
+
+ def read_raw(self):
+ """Read the sensor(s) but return the raw data, useful for debugging"""
+ return self.read()
diff --git a/sensors/linux/sensor.py b/sensors/linux/sensor.py
new file mode 100644
index 0000000..0a49947
--- /dev/null
+++ b/sensors/linux/sensor.py
@@ -0,0 +1,57 @@
+import re
+
+import board
+import digitalio
+
+from logger.Logger import Logger, LOG_LEVEL
+from sensors.base_sensor import BaseSensor
+
+
+# PIN MODE : OUT | IN
+
+class Sensor(BaseSensor):
+
+ def __init__(self, pin, name=None, key=None, redis_conn=None):
+
+ super().__init__(
+ pin=pin,
+ name=name,
+ key=key,
+ redis_conn=redis_conn
+ )
+ self.pin_obj = getattr(board, pin)
+
+ if re.match(r'D\d+$', pin):
+ self.is_digital = True
+ elif re.match(r'A\d+$', pin):
+ self.is_digital = False
+ else:
+ Logger.log(
+ LOG_LEVEL["error"],
+ "Cannot detect pin type (Digital or analog), "
+ "should be Dxx or Axx for digital or analog. "
+ "Please refer to "
+ "https://github.com/adafruit/Adafruit_Blinka/tree/master/src/adafruit_blinka/board"
+ )
+
+ self.gpio = digitalio
+
+ def read_pin(self):
+ """Read the pin from the board.
+
+ Pin value must be a blinka Pin.
+ D for a digital input and A for an analog input, followed by the
+ pin number.
+
+ You check the board-specific pin mapping
+ [here](https://github.com/adafruit/Adafruit_Blinka/blob/master/src/adafruit_blinka/board/).
+
+ Examples:
+ read_pin(board.D12)
+ read_pin(board.A12)
+ """
+ if self.is_digital:
+ data = self.gpio.DigitalInOut(self.pin_obj).value
+ else:
+ data = self.gpio.AnalogIn(self.pin_obj).value
+ return data
diff --git a/sensors/pi/i2c/__init__.py b/sensors/mcp3xxx/__init__.py
similarity index 100%
rename from sensors/pi/i2c/__init__.py
rename to sensors/mcp3xxx/__init__.py
diff --git a/sensors/mcp3xxx/sensor.py b/sensors/mcp3xxx/sensor.py
new file mode 100644
index 0000000..6b6a4b6
--- /dev/null
+++ b/sensors/mcp3xxx/sensor.py
@@ -0,0 +1,45 @@
+import adafruit_mcp3xxx.mcp3008 as MCP
+
+# Base sensor class to extend all other mcp3xxx sensors from.
+from sensors.base_sensor import BaseSensor
+
+
+class Sensor(BaseSensor):
+ PINS = {
+ 0: MCP.P0,
+ 1: MCP.P1,
+ 2: MCP.P2,
+ 3: MCP.P3,
+ 4: MCP.P4,
+ 5: MCP.P5,
+ 6: MCP.P6,
+ 7: MCP.P7,
+ }
+
+ def __init__(self, pin: int, mcp, name=None, key=None, redis_conn=None):
+ super().__init__(
+ pin=pin,
+ name=name,
+ key=key,
+ redis_conn=redis_conn
+ )
+ self.mcp = mcp
+ self.topic = None
+
+ def read_raw(self):
+ """
+ Read the sensor(s) but return the raw voltage, useful for debugging
+
+ Returns:
+
+ """
+ return self.topic.voltage
+
+ def read_pin(self):
+ """
+ Read the pin from the mcp3xxx as unaltered digital value
+
+ Returns:
+
+ """
+ return self.topic.value
diff --git a/sensors/MCP3xxx/soil_sensor.py b/sensors/mcp3xxx/soil_sensor.py
similarity index 70%
rename from sensors/MCP3xxx/soil_sensor.py
rename to sensors/mcp3xxx/soil_sensor.py
index 1a84d82..7911c99 100644
--- a/sensors/MCP3xxx/soil_sensor.py
+++ b/sensors/mcp3xxx/soil_sensor.py
@@ -1,14 +1,11 @@
-import time
-import datetime
-import json
-import redis
-from .sensor import Sensor
import sys
+
from adafruit_mcp3xxx.analog_in import AnalogIn
from logger.Logger import Logger, LOG_LEVEL
+from sensors.mcp3xxx.sensor import Sensor
+
-sys.path.append('..')
# Tested using Sun3Drucker Model SX239
# Wet Water = 287
@@ -21,15 +18,19 @@
class SoilSensor(Sensor):
def __init__(self, pin, mcp, name=None, key=None, redis_conn=None):
- super().__init__(pin, name=name, key=key, mcp=mcp, redis_conn=redis_conn)
+ super().__init__(pin, name=name, key=key, mcp=mcp,
+ redis_conn=redis_conn)
return
def init_sensor(self):
self.topic = AnalogIn(self.mcp, Sensor.PINS[self.pin])
def read(self):
- resistance = self.readPin()
- moistpercent = ((resistance - WaterBounds) / (AirBounds - WaterBounds)) * 100
+ resistance = self.read_pin()
+ moistpercent = (
+ (resistance - WaterBounds) / (
+ AirBounds - WaterBounds)
+ ) * 100
if moistpercent > 80:
moisture = 'Very Dry - ' + str(int(moistpercent))
elif 80 >= moistpercent > 45:
@@ -41,7 +42,8 @@ def read(self):
# print("Resistance: %d" % resistance)
# TODO: Put redis store into sensor worker
self.r.set(self.key,
- resistance) # TODO: CHANGE BACK TO 'moistpercent' (PERSONAL CONFIG)
+ resistance)
+ # TODO: CHANGE BACK TO 'moistpercent' (PERSONAL CONFIG)
Logger.log(LOG_LEVEL["debug"], "moisture: {0}".format(moisture))
return resistance
diff --git a/sensors/pi/float_sensor.py b/sensors/pi/float_sensor.py
deleted file mode 100644
index bc29236..0000000
--- a/sensors/pi/float_sensor.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import time
-import json
-import redis
-from .sensor import Sensor
-import RPi.GPIO as GPIO
-
-#PIN MODE : OUT | IN
-
-class FloatSensor(Sensor):
-
- def __init__(self, pin, name=None, key=None, redis_conn=None):
- super().__init__(pin, name=name, key=key, redis_conn=redis_conn)
- return
-
- def init_sensor(self):
- #Initialize the sensor here (i.e. set pin mode, get addresses, etc) this gets called by the worker
- #GPIO.setmode(GPIO.BCM)
- GPIO.setup(self.pin, GPIO.IN)
- return
-
- def read(self):
- #Read the sensor(s), parse the data and store it in redis if redis is configured
- value = GPIO.input(self.pin)
- return value
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- return self.read()
\ No newline at end of file
diff --git a/sensors/pi/humidity_sensor.py b/sensors/pi/humidity_sensor.py
deleted file mode 100644
index 2006ab2..0000000
--- a/sensors/pi/humidity_sensor.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import time
-import json
-import redis
-from .sensor import Sensor
-import RPi.GPIO as GPIO
-import Adafruit_DHT
-import sys
-sys.path.append('..')
-
-from logger.Logger import Logger, LOG_LEVEL
-
-#r = redis.Redis(host='127.0.0.1', port=6379)
-#PIN MODE : OUT | IN
-
-class HumiditySensor(Sensor):
-
- def __init__(self, pin, name=None, key=None, model='11', redis_conn=None):
- super().__init__(pin, name=name, key=key, redis_conn=redis_conn)
- self.type = model
- return
-
- def init_sensor(self):
- #Initialize the sensor here (i.e. set pin mode, get addresses, etc) this gets called by the worker
- sensor_types = { '11': Adafruit_DHT.DHT11,
- '22': Adafruit_DHT.DHT22,
- '2302': Adafruit_DHT.AM2302 }
- if self.type in sensor_types:
- self.sensor = sensor_types[self.type]
- else:
- Logger.log(LOG_LEVEL["warning"], 'Sensor Model Error: Defaulting to DHT11')
- self.sensor = Adafruit_DHT.DHT11
- return
-
- def read(self):
- #Read the sensor(s), parse the data and store it in redis if redis is configured
-
- humidity, temperature = Adafruit_DHT.read_retry(self.sensor, self.pin)
-
- if humidity is not None and temperature is not None:
- self.r.set(self.key + '_temperature', round(temperature * 1.8 + 32, 2))
- self.r.set(self.key + '_humidity', humidity)
- readings = {'temperature': round(temperature * 1.8 + 32, 2), 'humidity': round(humidity, 2)}
- self.r.set(self.key, json.dumps(readings))
- return readings
- else:
- Logger.log(LOG_LEVEL["error"], 'DHT Reading was Invalid. Trying again next cycle.')
- return None
-
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- return self.read()
diff --git a/sensors/pi/i2c/bme680_sensor.py b/sensors/pi/i2c/bme680_sensor.py
deleted file mode 100644
index 4ea1d97..0000000
--- a/sensors/pi/i2c/bme680_sensor.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import time
-import json
-import redis
-from .sensor import Sensor
-import board
-from busio import I2C
-import adafruit_bme680
-
-from logger.Logger import Logger, LOG_LEVEL
-
-import sys
-sys.path.append('..')
-
-import variables
-
-
-class Bme680Sensor(Sensor):
-
- def __init__(self, address = None, name=None, key=None, redis_conn=None):
- super().__init__(address, name=name, key=key, redis_conn=redis_conn)
- return
-
- def init_sensor(self):
- self.sensor = adafruit_bme680.Adafruit_BME680_I2C(self.i2c, debug=False)
- # change this to match the location's pressure (hPa) at sea level
- self.sensor.sea_level_pressure = 1013.25
- return
-
- def read(self):
- temperature = round((self.sensor.temperature - 5) * 1.8 + 32, 2)
- gas = self.sensor.gas
- humidity = round(self.sensor.humidity, 1)
- pressure = round(self.sensor.pressure, 2)
- altitude = round(self.sensor.altitude, 3)
-
- if humidity is not None and temperature is not None:
- self.r.set(self.key + '_temperature', temperature)
- self.r.set(self.key + '_humidity', humidity)
- self.r.set(self.key + '_gas', gas)
- self.r.set(self.key + '_pressure', pressure)
- self.r.set(self.key + '_altitude', altitude)
- readings = {'temperature': temperature, 'humidity': humidity, 'pressure': pressure, 'gas': gas, 'altitude': altitude}
- self.r.set(self.key, json.dumps(readings))
- # print('BME680:', readings)
- return readings
- else:
- Logger.log(LOG_LEVEL["error"], 'Failed to get reading [BME680]. Try again!')
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- return self.read()
diff --git a/sensors/pi/i2c/sensor.py b/sensors/pi/i2c/sensor.py
deleted file mode 100644
index cf9235b..0000000
--- a/sensors/pi/i2c/sensor.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import time
-import json
-import redis
-import board
-from busio import I2C
-import RPi.GPIO as GPIO
-
-#PIN MODE : OUT | IN
-
-class Sensor():
-
- def __init__(self, address, name=None, key=None, redis_conn=None):
- self.address = address
-
- if key is None:
- raise Exception('No "key" Found in I2C Sensor Config')
- else:
- self.key = key.replace(" ", "_").lower()
-
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
-
- self.gpio = GPIO
- self.i2c = I2C(board.SCL, board.SDA)
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- return
-
- def init_sensor(self):
- #Initialize the sensor here (i.e. set pin mode, get addresses, etc)
- #GPIO.setmode(GPIO.BCM)
- #GPIO.setup(pin, GPIO.IN)
- pass
-
- def read(self):
- #Read the sensor(s), parse the data and store it in redis if redis is configured
- #GPIO.input(pin)
- pass
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- pass
-
- def readPin(self):
- #Read the pin from the ardiuno. Can be analog or digital based on "analog_pin_mode"
- data = self.gpio.input(self.pin)
- return data
\ No newline at end of file
diff --git a/sensors/pi/sensor.py b/sensors/pi/sensor.py
deleted file mode 100644
index 657f9c6..0000000
--- a/sensors/pi/sensor.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import time
-import json
-import redis
-import RPi.GPIO as GPIO
-
-#PIN MODE : OUT | IN
-
-class Sensor():
-
- def __init__(self, pin, name=None, key=None, redis_conn=None):
- self.pin = pin
-
- if key is None:
- raise Exception('No "key" Found in Sensor Config')
- else:
- self.key = key.replace(" ", "_").lower()
-
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
-
- self.gpio = GPIO
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- return
-
- def init_sensor(self):
- #Initialize the sensor here (i.e. set pin mode, get addresses, etc)
- #GPIO.setmode(GPIO.BCM)
- #GPIO.setup(pin, GPIO.IN)
- pass
-
- def read(self):
- #Read the sensor(s), parse the data and store it in redis if redis is configured
- #GPIO.input(pin)
- pass
-
- def readRaw(self):
- #Read the sensor(s) but return the raw data, useful for debugging
- pass
-
- def readPin(self):
- #Read the pin from the ardiuno. Can be analog or digital based on "analog_pin_mode"
- data = self.gpio.input(self.pin)
- return data
\ No newline at end of file
diff --git a/server/mudpi_event_listener.js b/server/mudpi_event_listener.js
index 9f58f5b..1f2547e 100644
--- a/server/mudpi_event_listener.js
+++ b/server/mudpi_event_listener.js
@@ -12,13 +12,13 @@ const axios = require('axios')
const address = 'test.php'
const channel = '*';
let axiosConfig = {
- headers: {
+ headers: {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "*",
- },
- baseURL: 'http://192.168.2.230/',
- timeout: 1000,
- responseType: 'json'
+ },
+ baseURL: 'http://192.168.2.230/',
+ timeout: 1000,
+ responseType: 'json'
};
//------------------------------
@@ -81,22 +81,22 @@ plistener.on('pmessage', (pattern, channel, message) => {
console.log(`\x1b[36mPattern Message Received on \x1b[1m${channel}\x1b[0m`);
let eventPromise = relayEvent(message)
eventPromise.then((response) => {
- try {
- console.log(`\x1b[32mEvent Successfully Relayed. RESPONSE: %s\x1b[0m`, response.status)
-
- if(typeof response !== 'undefined' ) {
- if (response != null && response.hasOwnProperty('data')) {
- console.log('\x1b[32mResponse Data Received: \x1b[0m', response.data)
- }
- }
- }
- catch(error) {
- console.log('\x1b[32mEvent Successfully Relayed.\x1b[0m \x1b[31mRESPONSE: Error Decoding Response\x1b[0m')
- }
+ try {
+ console.log(`\x1b[32mEvent Successfully Relayed. RESPONSE: %s\x1b[0m`, response.status)
+
+ if(typeof response !== 'undefined' ) {
+ if (response != null && response.hasOwnProperty('data')) {
+ console.log('\x1b[32mResponse Data Received: \x1b[0m', response.data)
+ }
+ }
+ }
+ catch(error) {
+ console.log('\x1b[32mEvent Successfully Relayed.\x1b[0m \x1b[31mRESPONSE: Error Decoding Response\x1b[0m')
+ }
})
.catch((error) => {
- console.log('\x1b[31mRelaying Event FAILED:\x1b[0m')
- console.log(error)
+ console.log('\x1b[31mRelaying Event FAILED:\x1b[0m')
+ console.log(error)
})
//console.log(data)
console.log('\x1b[33mAttempting to Relay Event: \x1b[1m', message, '\x1b[0m')
@@ -104,35 +104,35 @@ plistener.on('pmessage', (pattern, channel, message) => {
async function relayEvent(event=null) {
- let relayedEvent = null
-
- try {
- relayedEvent = await axios.post(address, event, axiosConfig)
- }
- catch(e) {
- if(e.code == 'ENETUNREACH') {
- let retries = 3
- while(retries > 1 ) {
-
- try {
- console.log('\x1b[31mRelaying the Event Failed [', e.code, ']\x1b[0m')
- await sleep(5000)
- console.log('\x1b[33mRetrying Relaying the Event...\x1b[0m')
- relayedEvent = await axios.post(address, event, axiosConfig)
- }
- catch(e) {
- console.log('\x1b[31mProblem Resending the Event [', e.code, ']\x1b[0m')
- }
-
- retries--
- }
- }
- else {
- console.log('\x1b[31mProblem Relaying the Event:\x1b[0m')
- console.error(e)
- }
- }
-
- return relayedEvent
+ let relayedEvent = null
+
+ try {
+ relayedEvent = await axios.post(address, event, axiosConfig)
+ }
+ catch(e) {
+ if(e.code == 'ENETUNREACH') {
+ let retries = 3
+ while(retries > 1 ) {
+
+ try {
+ console.log('\x1b[31mRelaying the Event Failed [', e.code, ']\x1b[0m')
+ await sleep(5000)
+ console.log('\x1b[33mRetrying Relaying the Event...\x1b[0m')
+ relayedEvent = await axios.post(address, event, axiosConfig)
+ }
+ catch(e) {
+ console.log('\x1b[31mProblem Resending the Event [', e.code, ']\x1b[0m')
+ }
+
+ retries--
+ }
+ }
+ else {
+ console.log('\x1b[31mProblem Relaying the Event:\x1b[0m')
+ console.error(e)
+ }
+ }
+
+ return relayedEvent
}
diff --git a/server/mudpi_server.py b/server/mudpi_server.py
index af6ba8f..ce459c2 100644
--- a/server/mudpi_server.py
+++ b/server/mudpi_server.py
@@ -5,62 +5,80 @@
from logger.Logger import Logger, LOG_LEVEL
+
# A socket server prototype that was going to be used for devices to communicate.
# Instead we are using nodejs to catch events in redis and emit them over a socket.
# May update this in later version for device communications. Undetermined.
class MudpiServer(object):
- def __init__(self, system_running, host='127.0.0.1', port=7002):
- self.port = int(port)
- self.host = host
- self.system_running = system_running
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.client_threads = []
+ def __init__(self, system_running, host='127.0.0.1', port=7002):
+ self.port = int(port)
+ self.host = host
+ self.system_running = system_running
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.client_threads = []
+
+ try:
+ self.sock.bind((self.host, self.port))
+ except socket.error as msg:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'Failed to create socket. Error Code: ',
+ str(msg[0]), ' , Error Message: ', msg[1]
+ )
+ sys.exit()
- try:
- self.sock.bind((self.host, self.port))
- except socket.error as msg:
- Logger.log(LOG_LEVEL["error"], 'Failed to create socket. Error Code: ', str(msg[0]), ' , Error Message: ', msg[1])
- sys.exit()
+ def listen(self):
+ self.sock.listen(10) # number of clients to listen for
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'MudPi Server...\t\t\t\t\033[1;32m Online\033[0;0m ')
+ while self.system_running.is_set():
+ try:
+ client, address = self.sock.accept()
+ client.settimeout(60)
+ Logger.log(
+ LOG_LEVEL["debug"], 'Client connected from %s',
+ address
+ )
+ t = threading.Thread(target=self.listen_to_client,
+ args=(client, address))
+ self.client_threads.append(t)
+ t.start()
+ except:
+ pass
+ print('Server Shutdown...\r', end="", flush=True)
+ self.sock.close()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Server Shutdown...\t\t\t\033[1;32m Complete\033[0;0m'
+ )
- def listen(self):
- self.sock.listen(10) #number of clients to listen for
- Logger.log(LOG_LEVEL["info"], 'MudPi Server...\t\t\t\t\033[1;32m Online\033[0;0m ')
- while self.system_running.is_set():
- try:
- client, address = self.sock.accept()
- client.settimeout(60)
- Logger.log(LOG_LEVEL["debug"], 'Client connected from ', address)
- t = threading.Thread(target = self.listenToClient, args = (client, address))
- self.client_threads.append(t)
- t.start()
- except:
- pass
- print('Server Shutdown...\r', end="", flush=True)
- self.sock.close()
- Logger.log(LOG_LEVEL["info"], 'Server Shutdown...\t\t\t\033[1;32m Complete\033[0;0m')
+ def listen_to_client(self, client, address):
+ size = 1024
+ while self.system_running.is_set():
+ try:
+ data = pickle.loads(client.recv(size))
+ if data:
+ print(data)
+ response = data
+ client.send(pickle.dumps(data))
+ else:
+ raise Exception('Client Disconnected')
+ except:
+ client.close()
+ return False
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Closing Client Connection...\t\t\033[1;32m Complete\033[0;0m'
+ )
- def listenToClient(self, client, address):
- size = 1024
- while self.system_running.is_set():
- try:
- data = pickle.loads(client.recv(size))
- if data:
- print(data)
- response = data
- client.send(pickle.dumps(data))
- else:
- raise error('Client Disconnected')
- except:
- client.close()
- return false
- Logger.log(LOG_LEVEL["info"], 'Closing Client Connection...\t\t\033[1;32m Complete\033[0;0m')
if __name__ == "__main__":
- host = '127.0.0.1'
- port = 7002
- server = MudpiServer(host, port)
- server.listen();
- while True:
- pass
\ No newline at end of file
+ host = '127.0.0.1'
+ port = 7002
+ server = MudpiServer(host, port)
+ server.listen();
+ while True:
+ pass
diff --git a/tools/add_lcd_message.py b/tools/add_lcd_message.py
index df32557..22b69b5 100644
--- a/tools/add_lcd_message.py
+++ b/tools/add_lcd_message.py
@@ -9,11 +9,11 @@
old_messages = r.get('lcdmessages')
messages = []
if old_messages:
- messages = json.loads(old_messages.decode('utf-8'))
+ messages = json.loads(old_messages.decode('utf-8'))
testmessage = {'line_1': line1, 'line_2': line2}
messages.append(testmessage)
r.set('lcdmessages', json.dumps(messages))
-print('Value set in redis and in queue')
\ No newline at end of file
+print('Value set in redis and in queue')
diff --git a/tools/event_send_tool.py b/tools/event_send_tool.py
index c9b7ee0..183fab5 100644
--- a/tools/event_send_tool.py
+++ b/tools/event_send_tool.py
@@ -3,79 +3,88 @@
import json
import time
-def timedMessage(message, delay=3):
- for s in range(1,delay):
- remainingTime = delay - s
- print(message + '...{0}s \r'.format(remainingTime), end="", flush=True)
- time.sleep(s)
-if __name__ == "__main__":
- try:
- option = True
- message = {}
- r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
- publisher = r
- topic = None
- while option != 0:
- #Clear the screen command
- print(chr(27) + "[2J")
- print('--------- Redis MudPi ---------')
- print('|3. Test Event |')
- print('|2. Toggle |')
- print('|1. Switch |')
- print('|0. Shutdown |')
- print('-------------------------------')
- try:
- option = int(input('Enter Option: '))
- except:
- #Catch string input error
- option = 9
- if option != 0:
- if option == 1:
- try:
- new_state = int(input('Enter State to switch to (0 or 1): '))
- if new_state != 0 and new_state != 1:
- new_state = 0
- except:
- new_state = 0
- message = {
- 'event': 'Switch',
- 'data': new_state
- }
- elif option == 2:
- message = {
- 'event': 'Toggle',
- 'data': None
- }
- elif option == 3:
- message = {
- 'event': "StateChanged",
- 'data': "/home/pi/Desktop/mudpi/img/mudpi-0039-2019-04-14-02-21.jpg",
- 'source': "camera_1"
- }
- topic = 'garden/pi/camera'
- else:
- timedMessage('Option not recognized')
- print(chr(27) + "[2J")
- continue
+def timed_message(message, delay=3):
+ for s in range(1, delay):
+ remainingTime = delay - s
+ print(message + '...{0}s \r'.format(remainingTime), end="", flush=True)
+ time.sleep(s)
- if topic is None:
- topic = str(input('Enter Topic to Broadcast: '))
- if topic is not None and topic != '':
- #Publish the message
- publisher.publish(topic, json.dumps(message))
- print(message)
- timedMessage('Message Successfully Published!')
- else:
- timedMessage('Topic Input Invalid')
- time.sleep(2)
+if __name__ == "__main__":
+ try:
+ option = True
+ message = {}
+ r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
+ publisher = r
+ topic = None
+ while option != 0:
+ # Clear the screen command
+ print(chr(27) + "[2J")
+ print('--------- Redis MudPi ---------')
+ print('|3. Test Event |')
+ print('|2. Toggle |')
+ print('|1. Switch |')
+ print('|0. Shutdown |')
+ print('-------------------------------')
- print('Exit')
- except KeyboardInterrupt:
- #Kill The Server
- #r.publish('test', json.dumps({'EXIT':True}))
- print('Publish Program Terminated...')
- finally:
- pass
-
+ try:
+ option = int(input('Enter Option: '))
+ except:
+ # Catch string input error
+ option = 9
+
+ if option != 0:
+ if option == 1:
+ try:
+ new_state = int(
+ input('Enter State to switch to (0 or 1): '))
+ if new_state != 0 and new_state != 1:
+ new_state = 0
+ except:
+ new_state = 0
+ message = {
+ 'event': 'Switch',
+ 'data': new_state
+ }
+
+ elif option == 2:
+ message = {
+ 'event': 'Toggle',
+ 'data': None
+ }
+
+ elif option == 3:
+ message = {
+ 'event': "StateChanged",
+ 'data': "/home/pi/Desktop/mudpi/img/mudpi-0039-2019-04-14-02-21.jpg",
+ 'source': "camera_1"
+ }
+ topic = 'garden/pi/camera'
+
+ else:
+ timed_message('Option not recognized')
+ print(chr(27) + "[2J")
+ continue
+
+ if topic is None:
+ topic = str(input('Enter Topic to Broadcast: '))
+
+ if topic is not None and topic != '':
+ # Publish the message
+ publisher.publish(topic, json.dumps(message))
+ print(message)
+ timed_message('Message Successfully Published!')
+ else:
+ timed_message('Topic Input Invalid')
+ time.sleep(2)
+
+ print('Exit')
+
+ except KeyboardInterrupt:
+ # Kill The Server
+ # r.publish('test', json.dumps({'EXIT':True}))
+ print('Publish Program Terminated...')
+
+ finally:
+ pass
diff --git a/tools/lcd_message_tool.py b/tools/lcd_message_tool.py
index 9e63f1c..78d961a 100644
--- a/tools/lcd_message_tool.py
+++ b/tools/lcd_message_tool.py
@@ -3,94 +3,94 @@
import json
import time
-def timedMessage(message, delay=3):
- for s in range(1,delay):
- remainingTime = delay - s
- print(message + '...{0}s \r'.format(remainingTime), end="", flush=True)
- time.sleep(s)
+def timed_message(message, delay=3):
+ for s in range(1,delay):
+ remainingTime = delay - s
+ print(message + '...{0}s \r'.format(remainingTime), end="", flush=True)
+ time.sleep(s)
if __name__ == "__main__":
- try:
- option = True
- message = {}
- r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
- publisher = r
- topic = None
- while option != 0:
- #Clear the screen command
- print(chr(27) + "[2J")
- print('--------- LCD MudPi ---------')
- print('|4. Clear Message Queue |')
- print('|3. Clear Display |')
- print('|2. Test Message |')
- print('|1. Add Message |')
- print('|0. Shutdown |')
- print('-------------------------------')
- try:
- option = int(input('Enter Option: '))
- except:
- #Catch string input error
- option = 9
- if option != 0:
- if option == 1:
- try:
- msg = {
- "message":"",
- "duration":10
- }
- msg["message"] = str(input('Enter Message to Display: '))
- msg["duration"] = int(input('Enter Duration to Display (seconds): '))
-
- except:
- msg = {
- "message":"Error Test",
- "duration":10
- }
- message = {
- 'event': 'Message',
- 'data': msg
- }
- elif option == 2:
- msg = {
- "message":"Test Message\nMudPi Test",
- "duration":15
- }
- message = {
- 'event': 'Message',
- 'data': msg
- }
- elif option == 3:
- message = {
- 'event': 'Clear',
- 'data': 1
- }
- elif option == 4:
- message = {
- 'event': 'ClearQueue',
- 'data': 1
- }
- else:
- timedMessage('Option not recognized')
- print(chr(27) + "[2J")
- continue
+ try:
+ option = True
+ message = {}
+ r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
+ publisher = r
+ topic = None
+ while option != 0:
+ #Clear the screen command
+ print(chr(27) + "[2J")
+ print('--------- LCD MudPi ---------')
+ print('|4. Clear Message Queue |')
+ print('|3. Clear Display |')
+ print('|2. Test Message |')
+ print('|1. Add Message |')
+ print('|0. Shutdown |')
+ print('-------------------------------')
+ try:
+ option = int(input('Enter Option: '))
+ except:
+ #Catch string input error
+ option = 9
+ if option != 0:
+ if option == 1:
+ try:
+ msg = {
+ "message":"",
+ "duration":10
+ }
+ msg["message"] = str(input('Enter Message to Display: '))
+ msg["duration"] = int(input('Enter Duration to Display (seconds): '))
+
+ except:
+ msg = {
+ "message":"Error Test",
+ "duration":10
+ }
+ message = {
+ 'event': 'Message',
+ 'data': msg
+ }
+ elif option == 2:
+ msg = {
+ "message":"Test Message\nMudPi Test",
+ "duration":15
+ }
+ message = {
+ 'event': 'Message',
+ 'data': msg
+ }
+ elif option == 3:
+ message = {
+ 'event': 'Clear',
+ 'data': 1
+ }
+ elif option == 4:
+ message = {
+ 'event': 'ClearQueue',
+ 'data': 1
+ }
+ else:
+ timed_message('Option not recognized')
+ print(chr(27) + "[2J")
+ continue
- if topic is None:
- topic = str(input('Enter the LCD Topic to Broadcast: '))
+ if topic is None:
+ topic = str(input('Enter the LCD Topic to Broadcast: '))
- if topic is not None and topic != '':
- #Publish the message
- publisher.publish(topic, json.dumps(message))
- print(message)
- timedMessage('Message Successfully Queued!')
- else:
- timedMessage('Topic Input Invalid')
- time.sleep(2)
+ if topic is not None and topic != '':
+ #Publish the message
+ publisher.publish(topic, json.dumps(message))
+ print(message)
+ timed_message('Message Successfully Queued!')
+ else:
+ timed_message('Topic Input Invalid')
+ time.sleep(2)
- print('Exit')
- except KeyboardInterrupt:
- #Kill The Server
- #r.publish('test', json.dumps({'EXIT':True}))
- print('LCD Message Program Terminated...')
- finally:
- pass
-
+ print('Exit')
+ except KeyboardInterrupt:
+ #Kill The Server
+ #r.publish('test', json.dumps({'EXIT':True}))
+ print('LCD Message Program Terminated...')
+ finally:
+ pass
+
diff --git a/tools/lcd_reset.py b/tools/lcd_reset.py
index adf0de8..26e774c 100644
--- a/tools/lcd_reset.py
+++ b/tools/lcd_reset.py
@@ -1,5 +1,5 @@
#!/usr/bin/python
-
+
# The wiring for the LCD is as follows:
# 1 : GND
# 2 : 5V
@@ -17,139 +17,143 @@
# 14: Data Bit 7
# 15: LCD Backlight +5V**
# 16: LCD Backlight GND
-
-#import
+
+# import
import RPi.GPIO as GPIO
import time
-
+
# Define GPIO to LCD mapping
LCD_RS = 7
-LCD_E = 8
+LCD_E = 8
LCD_D4 = 25
LCD_D5 = 24
LCD_D6 = 23
LCD_D7 = 18
-
+
# Define some device constants
-LCD_WIDTH = 16 # Maximum characters per line
+LCD_WIDTH = 16 # Maximum characters per line
LCD_CHR = True
LCD_CMD = False
-
-LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
-LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
-
+
+LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
+LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
+
# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005
+
def main():
-
- prepare_gpio()
-
- print('Clearing Lcd...')
- # Send some test
- lcd_string('LCD SCREEN RESET',LCD_LINE_1)
- lcd_string('3 SECONDS LEFT',LCD_LINE_2)
-
- time.sleep(3) # 3 second delay
-
- lcd_byte(0x01, LCD_CMD)
- lcd_string("RESET IN PROGRES",LCD_LINE_1)
- lcd_string("Shutting Down: 1s",LCD_LINE_2)
- time.sleep(1)
- lcd_string(" ",LCD_LINE_1)
- lcd_string(" ",LCD_LINE_2)
- time.sleep(E_DELAY)
- lcd_byte(0x01, LCD_CMD)
- time.sleep(E_DELAY)
- lcd_byte(0x01, LCD_CMD)
- time.sleep(E_DELAY)
- GPIO.cleanup()
+ prepare_gpio()
+
+ print('Clearing Lcd...')
+ # Send some test
+ lcd_string('LCD SCREEN RESET', LCD_LINE_1)
+ lcd_string('3 SECONDS LEFT', LCD_LINE_2)
+
+ time.sleep(3) # 3 second delay
+
+ lcd_byte(0x01, LCD_CMD)
+ lcd_string("RESET IN PROGRES", LCD_LINE_1)
+ lcd_string("Shutting Down: 1s", LCD_LINE_2)
+ time.sleep(1)
+ lcd_string(" ", LCD_LINE_1)
+ lcd_string(" ", LCD_LINE_2)
+ time.sleep(E_DELAY)
+ lcd_byte(0x01, LCD_CMD)
+ time.sleep(E_DELAY)
+ lcd_byte(0x01, LCD_CMD)
+ time.sleep(E_DELAY)
+ GPIO.cleanup()
def prepare_gpio():
- # Main program block
- GPIO.setwarnings(False)
- GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers
- GPIO.setup(LCD_E, GPIO.OUT) # E
- GPIO.setup(LCD_RS, GPIO.OUT) # RS
- GPIO.setup(LCD_D4, GPIO.OUT) # DB4
- GPIO.setup(LCD_D5, GPIO.OUT) # DB5
- GPIO.setup(LCD_D6, GPIO.OUT) # DB6
- GPIO.setup(LCD_D7, GPIO.OUT) # DB7
- # Initialise display
- lcd_init()
-
-
+ # Main program block
+ GPIO.setwarnings(False)
+ GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers
+ GPIO.setup(LCD_E, GPIO.OUT) # E
+ GPIO.setup(LCD_RS, GPIO.OUT) # RS
+ GPIO.setup(LCD_D4, GPIO.OUT) # DB4
+ GPIO.setup(LCD_D5, GPIO.OUT) # DB5
+ GPIO.setup(LCD_D6, GPIO.OUT) # DB6
+ GPIO.setup(LCD_D7, GPIO.OUT) # DB7
+ # Initialise display
+ lcd_init()
+
+
def lcd_init():
- # Initialise display
- lcd_byte(0x33,LCD_CMD) # 110011 Initialise
- lcd_byte(0x32,LCD_CMD) # 110010 Initialise
- lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
- lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
- lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
- lcd_byte(0x01,LCD_CMD) # 000001 Clear display
- time.sleep(E_DELAY)
-
+ # Initialise display
+ lcd_byte(0x33, LCD_CMD) # 110011 Initialise
+ lcd_byte(0x32, LCD_CMD) # 110010 Initialise
+ lcd_byte(0x06, LCD_CMD) # 000110 Cursor move direction
+ lcd_byte(0x0C, LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
+ lcd_byte(0x28, LCD_CMD) # 101000 Data length, number of lines, font size
+ lcd_byte(0x01, LCD_CMD) # 000001 Clear display
+ time.sleep(E_DELAY)
+
+
def lcd_byte(bits, mode):
- # Send byte to data pins
- # bits = data
- # mode = True for character
- # False for command
-
- GPIO.output(LCD_RS, mode) # RS
-
- # High bits
- GPIO.output(LCD_D4, False)
- GPIO.output(LCD_D5, False)
- GPIO.output(LCD_D6, False)
- GPIO.output(LCD_D7, False)
- if bits&0x10==0x10:
- GPIO.output(LCD_D4, True)
- if bits&0x20==0x20:
- GPIO.output(LCD_D5, True)
- if bits&0x40==0x40:
- GPIO.output(LCD_D6, True)
- if bits&0x80==0x80:
- GPIO.output(LCD_D7, True)
-
- # Toggle 'Enable' pin
- lcd_toggle_enable()
-
- # Low bits
- GPIO.output(LCD_D4, False)
- GPIO.output(LCD_D5, False)
- GPIO.output(LCD_D6, False)
- GPIO.output(LCD_D7, False)
- if bits&0x01==0x01:
- GPIO.output(LCD_D4, True)
- if bits&0x02==0x02:
- GPIO.output(LCD_D5, True)
- if bits&0x04==0x04:
- GPIO.output(LCD_D6, True)
- if bits&0x08==0x08:
- GPIO.output(LCD_D7, True)
-
- # Toggle 'Enable' pin
- lcd_toggle_enable()
-
+ # Send byte to data pins
+ # bits = data
+ # mode = True for character
+ # False for command
+
+ GPIO.output(LCD_RS, mode) # RS
+
+ # High bits
+ GPIO.output(LCD_D4, False)
+ GPIO.output(LCD_D5, False)
+ GPIO.output(LCD_D6, False)
+ GPIO.output(LCD_D7, False)
+ if bits & 0x10 == 0x10:
+ GPIO.output(LCD_D4, True)
+ if bits & 0x20 == 0x20:
+ GPIO.output(LCD_D5, True)
+ if bits & 0x40 == 0x40:
+ GPIO.output(LCD_D6, True)
+ if bits & 0x80 == 0x80:
+ GPIO.output(LCD_D7, True)
+
+ # Toggle 'Enable' pin
+ lcd_toggle_enable()
+
+ # Low bits
+ GPIO.output(LCD_D4, False)
+ GPIO.output(LCD_D5, False)
+ GPIO.output(LCD_D6, False)
+ GPIO.output(LCD_D7, False)
+ if bits & 0x01 == 0x01:
+ GPIO.output(LCD_D4, True)
+ if bits & 0x02 == 0x02:
+ GPIO.output(LCD_D5, True)
+ if bits & 0x04 == 0x04:
+ GPIO.output(LCD_D6, True)
+ if bits & 0x08 == 0x08:
+ GPIO.output(LCD_D7, True)
+
+ # Toggle 'Enable' pin
+ lcd_toggle_enable()
+
+
def lcd_toggle_enable():
- # Toggle enable
- time.sleep(E_DELAY)
- GPIO.output(LCD_E, True)
- time.sleep(E_PULSE)
- GPIO.output(LCD_E, False)
- time.sleep(E_DELAY)
-
-def lcd_string(message,line):
- # Send string to display
-
- message = message.ljust(LCD_WIDTH," ")
-
- lcd_byte(line, LCD_CMD)
-
- for i in range(LCD_WIDTH):
- lcd_byte(ord(message[i]),LCD_CHR)
-
+ # Toggle enable
+ time.sleep(E_DELAY)
+ GPIO.output(LCD_E, True)
+ time.sleep(E_PULSE)
+ GPIO.output(LCD_E, False)
+ time.sleep(E_DELAY)
+
+
+def lcd_string(message, line):
+ # Send string to display
+
+ message = message.ljust(LCD_WIDTH, " ")
+
+ lcd_byte(line, LCD_CMD)
+
+ for i in range(LCD_WIDTH):
+ lcd_byte(ord(message[i]), LCD_CHR)
+
+
if __name__ == '__main__':
- main()
\ No newline at end of file
+ main()
diff --git a/triggers/control_trigger.py b/triggers/control_trigger.py
index 4943ad6..f1da0a0 100644
--- a/triggers/control_trigger.py
+++ b/triggers/control_trigger.py
@@ -3,63 +3,92 @@
import redis
import sys
from .trigger import Trigger
-sys.path.append('..')
+
+
from logger.Logger import Logger, LOG_LEVEL
+
class ControlTrigger(Trigger):
- def __init__(self, main_thread_running, system_ready, name='ControlTrigger',key=None, source=None, thresholds=None, topic="controls", trigger_active=None, frequency='once', actions=[], group=None, redis_conn=None, sequences=[]):
- 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, sequences=sequences)
- self.topic = topic.replace(" ", "_").lower() if topic is not None else "controls"
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- return
+ def __init__(
+ self, main_thread_running, system_ready, name='ControlTrigger',
+ key=None, source=None, thresholds=None, topic="controls",
+ trigger_active=None, frequency='once', actions=[],
+ group=None, redis_conn=None, sequences=[]):
+ 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,
+ sequences=sequences
+ )
+ self.topic = topic.replace(" ",
+ "_").lower() if topic is not None else "controls"
+
+ try:
+ self.r = redis_conn if redis_conn is not None else redis.Redis(
+ host='127.0.0.1', port=6379)
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+ return
+
+ def init_trigger(self):
+ # Initialize the trigger here (i.e. set listeners or create cron jobs)
+ # Pubsub Listeners
+ self.pubsub = self.r.pubsub()
+ self.pubsub.subscribe(**{self.topic: self.handle_event})
+ pass
+
+ 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()
+ time.sleep(self.trigger_interval)
+ else:
+ time.sleep(2)
+ return
- def init_trigger(self):
- #Initialize the trigger here (i.e. set listeners or create cron jobs)
- #Pubsub Listeners
- self.pubsub = self.r.pubsub()
- self.pubsub.subscribe(**{self.topic: self.handleEvent})
- pass
+ def handle_event(self, message):
+ data = message['data']
+ if data is not None:
+ decoded_message = super().decode_event_data(data)
- 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()
- time.sleep(self.trigger_interval)
- else:
- time.sleep(2)
- return
+ try:
+ if decoded_message['event'] == 'ControlUpdate':
+ control_value = self.parse_control_data(
+ decoded_message["data"]
+ )
+ if super().evaluate_thresholds(control_value):
+ self.trigger_active.set()
+ if self.previous_state != self.trigger_active.is_set():
+ super().trigger(decoded_message['event'])
+ else:
+ if self.frequency == 'many':
+ super().trigger(decoded_message['event'])
+ else:
+ self.trigger_active.clear()
- def handleEvent(self, message):
- data = message['data']
- if data is not None:
- decoded_message = super().decodeEventData(data)
- try:
- if decoded_message['event'] == 'ControlUpdate':
- control_value = self.parseControlData(decoded_message["data"])
- if super().evaluateThresholds(control_value):
- self.trigger_active.set()
- if self.previous_state != self.trigger_active.is_set():
- super().trigger(decoded_message['event'])
- else:
- if self.frequency == 'many':
- super().trigger(decoded_message['event'])
- else:
- self.trigger_active.clear()
- except:
- Logger.log(LOG_LEVEL["error"], 'Error During Trigger Actions {0}'.format(self.key))
- self.previous_state = self.trigger_active.is_set()
+ except:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'Error During Trigger Actions {0}'.format(self.key)
+ )
+ self.previous_state = self.trigger_active.is_set()
- def parseControlData(self, data):
- parsed_data = data.get(self.source, None)
- return parsed_data
+ def parse_control_data(self, data):
+ parsed_data = data.get(self.source, None)
+ return parsed_data
- def shutdown(self):
- self.pubsub.close()
- return
\ No newline at end of file
+ def shutdown(self):
+ self.pubsub.close()
+ return
diff --git a/triggers/sensor_trigger.py b/triggers/sensor_trigger.py
index 5bd3281..f0fa108 100644
--- a/triggers/sensor_trigger.py
+++ b/triggers/sensor_trigger.py
@@ -3,64 +3,91 @@
import redis
import sys
from .trigger import Trigger
-sys.path.append('..')
+
+
from logger.Logger import Logger, LOG_LEVEL
+
class SensorTrigger(Trigger):
- def __init__(self, main_thread_running, system_ready, name='SensorTrigger',key=None, source=None, nested_source=None, thresholds=None, topic="sensors", trigger_active=None, frequency='once', actions=[], group=None, redis_conn=None, sequences=[]):
- 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, sequences=sequences)
- self.topic = topic.replace(" ", "_").lower() if topic is not None else "sensors"
- self.nested_source = nested_source.lower() if nested_source is not None else nested_source
- try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- return
+ def __init__(
+ self, main_thread_running, system_ready, name='SensorTrigger',
+ key=None, source=None, nested_source=None, thresholds=None,
+ topic="sensors", trigger_active=None, frequency='once',
+ actions=[], group=None, redis_conn=None, sequences=[]):
+ 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,
+ sequences=sequences
+ )
+ self.topic = topic.replace(" ",
+ "_").lower() if topic is not None else "sensors"
+ self.nested_source = nested_source.lower() if nested_source is not None else nested_source
+ try:
+ self.r = redis_conn if redis_conn is not None else redis.Redis(
+ host='127.0.0.1', port=6379)
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+ return
- def init_trigger(self):
- #Initialize the trigger here (i.e. set listeners or create cron jobs)
- #Pubsub Listeners
- self.pubsub = self.r.pubsub()
- self.pubsub.subscribe(**{self.topic: self.handleEvent})
- pass
+ def init_trigger(self):
+ # Initialize the trigger here (i.e. set listeners or create cron jobs)
+ # Pubsub Listeners
+ self.pubsub = self.r.pubsub()
+ self.pubsub.subscribe(**{self.topic: self.handle_event})
+ pass
- 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()
- time.sleep(self.trigger_interval)
- else:
- time.sleep(2)
- return
+ 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()
+ time.sleep(self.trigger_interval)
+ else:
+ time.sleep(2)
+ return
- def handleEvent(self, message):
- data = message['data']
- if data is not None:
- decoded_message = super().decodeEventData(data)
- try:
- if decoded_message['event'] == 'SensorUpdate' or decoded_message['event'] == 'PiSensorUpdate':
- sensor_value = self.parseSensorData(decoded_message["data"])
- if super().evaluateThresholds(sensor_value):
- self.trigger_active.set()
- if self.previous_state != self.trigger_active.is_set():
- super().trigger(decoded_message)
- else:
- if self.frequency == 'many':
- super().trigger(decoded_message)
- else:
- self.trigger_active.clear()
- except:
- Logger.log(LOG_LEVEL["error"], 'Error Triggering Actions for {0}'.format(self.name))
- self.previous_state = self.trigger_active.is_set()
+ def handle_event(self, message):
+ data = message['data']
+ if data is not None:
+ decoded_message = super().decode_event_data(data)
+ try:
+ if decoded_message['event'] == 'SensorUpdate' or \
+ decoded_message['event'] == 'PiSensorUpdate':
+ sensor_value = self.parse_sensor_data(
+ decoded_message["data"])
+ if super().evaluate_thresholds(sensor_value):
+ self.trigger_active.set()
+ if self.previous_state != self.trigger_active.is_set():
+ super().trigger(decoded_message)
+ else:
+ if self.frequency == 'many':
+ super().trigger(decoded_message)
+ else:
+ self.trigger_active.clear()
+ except:
+ Logger.log(LOG_LEVEL["error"],
+ 'Error Triggering Actions for {0}'.format(
+ self.name))
+ self.previous_state = self.trigger_active.is_set()
- def parseSensorData(self, data):
- parsed_data = data.get(self.source).get(self.nested_source, None) if self.nested_source is not None else data.get(self.source, None)
- return parsed_data
+ def parse_sensor_data(self, data):
+ parsed_data = data.get(self.source).get(self.nested_source,
+ None) if self.nested_source is not None else data.get(
+ self.source, None)
+ return parsed_data
- def shutdown(self):
- self.pubsub.close()
- return
\ No newline at end of file
+ def shutdown(self):
+ self.pubsub.close()
+ return
diff --git a/triggers/time_trigger.py b/triggers/time_trigger.py
index e33f9d3..fecdb22 100644
--- a/triggers/time_trigger.py
+++ b/triggers/time_trigger.py
@@ -3,45 +3,67 @@
import redis
import sys
from .trigger import Trigger
+
try:
- import pycron
- CRON_ENABLED = True
+ import pycron
+
+ CRON_ENABLED = True
except ImportError:
- CRON_ENABLED = False
-sys.path.append('..')
+ CRON_ENABLED = False
+
from logger.Logger import Logger, LOG_LEVEL
+
class TimeTrigger(Trigger):
- def __init__(self, main_thread_running, system_ready, name='TimeTrigger',key=None, trigger_active=None, actions=[], schedule=None, group=None, sequences=[]):
- super().__init__(main_thread_running, system_ready, name=name, key=key, trigger_active=trigger_active, actions=actions, trigger_interval=60, group=group, sequences=sequences)
- self.schedule = schedule
- return
-
- def init_trigger(self):
- #Initialize the trigger here (i.e. set listeners or create cron jobs)
- pass
-
- 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:
- Logger.log(LOG_LEVEL["error"], "Error pycron not found.")
- except:
- Logger.log(LOG_LEVEL["error"], "Error evaluating time trigger schedule.")
- time.sleep(self.trigger_interval)
- else:
- time.sleep(2)
- return
-
- def shutdown(self):
- return
\ No newline at end of file
+ def __init__(
+ self, main_thread_running, system_ready, name='TimeTrigger',
+ key=None, trigger_active=None, actions=[], schedule=None,
+ group=None, sequences=[]):
+ super().__init__(
+ main_thread_running,
+ system_ready,
+ name=name,
+ key=key,
+ trigger_active=trigger_active,
+ actions=actions,
+ trigger_interval=60,
+ group=group,
+ sequences=sequences
+ )
+ self.schedule = schedule
+ return
+
+ def init_trigger(self):
+ # Initialize the trigger here (i.e. set listeners or create cron jobs)
+ pass
+
+ 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:
+ Logger.log(
+ LOG_LEVEL["error"],
+ "Error pycron not found."
+ )
+ except:
+ Logger.log(
+ LOG_LEVEL["error"],
+ "Error evaluating time trigger schedule."
+ )
+ time.sleep(self.trigger_interval)
+ else:
+ time.sleep(2)
+ return
+
+ def shutdown(self):
+ return
diff --git a/triggers/trigger.py b/triggers/trigger.py
index 780092b..4e711e7 100644
--- a/triggers/trigger.py
+++ b/triggers/trigger.py
@@ -3,123 +3,138 @@
import redis
import threading
import sys
-sys.path.append('..')
+
+
from logger.Logger import Logger, LOG_LEVEL
+
class Trigger():
- def __init__(self, main_thread_running, system_ready, name=None, key=None, source=None, thresholds=None, trigger_active=None, frequency='once', actions=[], trigger_interval=1, group=None, sequences=[]):
-
- if key is None:
- raise Exception('No "key" Found in Trigger Config')
- else:
- self.key = key.replace(" ", "_").lower()
-
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
-
- self.thresholds = thresholds
- self.source = source.lower() if source is not None else source
- self.trigger_interval = trigger_interval
- self.actions = actions
- self.sequences = sequences
- 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()
- # Main thread events
- self.main_thread_running = main_thread_running
- self.system_ready = system_ready
- return
-
- def init_trigger(self):
- # Initialize the trigger here (i.e. set listeners or create cron jobs)
- pass
-
- 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):
- t = threading.Thread(target=self.check, args=())
- t.start()
- return t
-
- def trigger(self, value=None):
- try:
- if self.group is None:
- # Trigger the actions of the trigger
- for action in self.actions:
- action.trigger(value)
- # Trigger the sequences of the trigger
- for sequence in self.sequences:
- sequence.update(value)
- else:
- self.group.trigger()
- except Exception as e:
- Logger.log(LOG_LEVEL["error"], "Error triggering action {0} ".format(self.key), e)
- pass
- return
-
-
- def evaluateThresholds(self, value):
- thresholds_passed = False
- for threshold in self.thresholds:
- comparison = threshold.get("comparison", "eq")
- if comparison == "eq":
- if value == threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "ne":
- if value != threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "gt":
- if value > threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "gte":
- if value >= threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "lt":
- if value < threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "lte":
- if value <= threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- return thresholds_passed
-
- def decodeEventData(self, message):
- if isinstance(message, dict):
- #print('Dict Found')
- return message
- elif isinstance(message.decode('utf-8'), str):
- try:
- temp = json.loads(message.decode('utf-8'))
- #print('Json Found')
- return temp
- except:
- #print('Json Error. Str Found')
- return {'event':'Unknown', 'data':message}
- else:
- #print('Failed to detect type')
- return {'event':'Unknown', 'data':message}
-
- def shutdown(self):
- # Put any closing functions here that should be called as MudPi shutsdown (i.e. close connections)
- return
\ No newline at end of file
+ def __init__(self, main_thread_running, system_ready, name=None, key=None,
+ source=None, thresholds=None, trigger_active=None,
+ frequency='once', actions=[], trigger_interval=1, group=None,
+ sequences=[]):
+
+ if key is None:
+ raise Exception('No "key" Found in Trigger Config')
+ else:
+ self.key = key.replace(" ", "_").lower()
+
+ if name is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = name
+
+ self.thresholds = thresholds
+ self.source = source.lower() if source is not None else source
+ self.trigger_interval = trigger_interval
+ self.actions = actions
+ self.sequences = sequences
+ 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()
+ # Main thread events
+ self.main_thread_running = main_thread_running
+ self.system_ready = system_ready
+ return
+
+ def init_trigger(self):
+ # Initialize the trigger here (i.e. set listeners or create cron jobs)
+ pass
+
+ 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):
+ t = threading.Thread(target=self.check, args=())
+ t.start()
+ return t
+
+ def trigger(self, value=None):
+ try:
+ if self.group is None:
+ # Trigger the actions of the trigger
+ for action in self.actions:
+ action.trigger(value)
+ # Trigger the sequences of the trigger
+ for sequence in self.sequences:
+ sequence.update(value)
+ else:
+ self.group.trigger()
+ except Exception as e:
+ Logger.log(LOG_LEVEL["error"],
+ "Error triggering action {0} ".format(self.key), e)
+ pass
+ return
+
+ def evaluate_thresholds(self, value):
+ thresholds_passed = False
+ for threshold in self.thresholds:
+ comparison = threshold.get("comparison", "eq")
+ if comparison == "eq":
+ if value == threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+
+ elif comparison == "ne":
+ if value != threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+
+ elif comparison == "gt":
+ if value > threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+
+ elif comparison == "gte":
+ if value >= threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+
+ elif comparison == "lt":
+ if value < threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+
+ elif comparison == "lte":
+ if value <= threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+
+ return thresholds_passed
+
+ def decode_event_data(self, message):
+ if isinstance(message, dict):
+ # print('Dict Found')
+ return message
+
+ elif isinstance(message.decode('utf-8'), str):
+ try:
+ temp = json.loads(message.decode('utf-8'))
+ # print('Json Found')
+ return temp
+ except:
+ # print('Json Error. Str Found')
+ return {'event': 'Unknown', 'data': message}
+
+ else:
+ # print('Failed to detect type')
+ return {'event': 'Unknown', 'data': message}
+
+ def shutdown(self):
+ # Put any closing functions here that should be called as
+ # MudPi shutsdown (i.e. close connections)
+ return
diff --git a/triggers/trigger_group.py b/triggers/trigger_group.py
index 192c0e4..86e80cc 100644
--- a/triggers/trigger_group.py
+++ b/triggers/trigger_group.py
@@ -3,70 +3,82 @@
import redis
import threading
import sys
-sys.path.append('..')
+
+
from logger.Logger import Logger, LOG_LEVEL
+
class TriggerGroup():
- def __init__(self, name='TriggerGroup', key=None, triggers=[], group_active=None, frequency='once', actions=[], sequences=[]):
- if key is None:
- raise Exception('No "key" Found in Trigger Group Config')
- else:
- self.key = key.replace(" ", "_").lower()
+ def __init__(self, name='TriggerGroup', key=None, triggers=[],
+ group_active=None, frequency='once', actions=[],
+ sequences=[]):
+ if key is None:
+ raise Exception('No "key" Found in Trigger Group Config')
+ else:
+ self.key = key.replace(" ", "_").lower()
+
+ if name is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = name
+ 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:
- if name is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = name
- 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
+ for action in self.actions:
+ action.trigger(value)
- def add_trigger(self, trigger):
- self.triggers.append(trigger)
- pass
+ for sequence in self.sequences:
+ sequence.update(value)
- 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
+ else:
+ if self.frequency == 'many':
+ for action in self.actions:
+ action.trigger(value)
- 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)
- for sequence in self.sequences:
- sequence.update(value)
- else:
- if self.frequency == 'many':
- for action in self.actions:
- action.trigger(value)
- for sequence in self.sequences:
- sequence.update(value)
- else:
- self.trigger_count = 0
- except Exception as e:
- Logger.log(LOG_LEVEL["error"], "Error triggering group {0} ".format(self.key), e)
- pass
- return
+ for sequence in self.sequences:
+ sequence.update(value)
+ else:
+ self.trigger_count = 0
+ except Exception as e:
+ Logger.log(
+ LOG_LEVEL["error"],
+ "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
\ No newline at end of file
+ def shutdown(self):
+ # Put any closing functions here that should be called as
+ # MudPi shutsdown (i.e. close connections)
+ return
diff --git a/utils.py b/utils.py
new file mode 100644
index 0000000..66dc4fa
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,35 @@
+import json
+
+
+def load_config_json():
+ """
+
+ Returns:
+ Dictionary from json file.
+ """
+
+ with open('mudpi.config') as loadedfile:
+ configs = json.load(loadedfile)
+ loadedfile.close()
+ return configs
+
+
+def get_config_item(config, item, default=None, replace_char='_'):
+ """
+
+ Args:
+ config:
+ item:
+ default:
+ replace_char:
+
+ Returns:
+ Configuration item
+ """
+
+ value = config.get(item, default)
+
+ if type(value) == str:
+ value = value.replace(" ", replace_char).lower()
+
+ return value
diff --git a/variables.py b/variables.py
deleted file mode 100644
index 1388c2d..0000000
--- a/variables.py
+++ /dev/null
@@ -1,5 +0,0 @@
-PREVIOUS_LINE="\x1b[1F"
-RED_BACK="\x1b[41;37m"
-GREEN_BACK="\x1b[42;30m"
-YELLOW_BACK="\x1b[43;30m"
-RESET="\x1b[0m"
diff --git a/workers/adc_worker.py b/workers/adc_worker.py
index cc5654f..f9cfc42 100644
--- a/workers/adc_worker.py
+++ b/workers/adc_worker.py
@@ -41,7 +41,8 @@ def __init__(self, config: dict, main_thread_running, system_ready):
self.system_ready = system_ready
self.node_ready = False
try:
- self.r = redis_conn if redis_conn is not None else redis.Redis(host='127.0.0.1', port=6379)
+ self.r = redis_conn if redis_conn is not None else redis.Redis(
+ host='127.0.0.1', port=6379)
except KeyError:
self.r = redis.Redis(host='127.0.0.1', port=6379)
@@ -71,28 +72,45 @@ def init_sensors(self):
for sensor in self.config['sensors']:
if sensor.get('type', None) is not None:
- # Get the sensor from the sensors folder {sensor name}_sensor.{SensorName}Sensor
- sensor_type = 'sensors.MCP3xxx.' + sensor.get('type').lower() + '_sensor.' + sensor.get(
+ # Get the sensor from the sensors folder
+ # {sensor name}_sensor.{SensorName}Sensor
+ sensor_type = 'sensors.mcp3xxx.' + sensor.get(
+ 'type').lower() + '_sensor.' + sensor.get(
'type').capitalize() + 'Sensor'
# analog_pin_mode = False if sensor.get('is_digital', False) else True
imported_sensor = self.dynamic_sensor_import(sensor_type)
new_sensor = imported_sensor(int(sensor.get('pin')),
- name=sensor.get('name', sensor.get('type')),
+ name=sensor.get('name',
+ sensor.get(
+ 'type')),
key=sensor.get('key', None),
mcp=self.mcp)
new_sensor.init_sensor()
self.sensors.append(new_sensor)
- Logger.log(LOG_LEVEL["info"], '{type} Sensor {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(**sensor))
+ Logger.log(
+ LOG_LEVEL["info"],
+ '{type} Sensor {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(
+ **sensor)
+ )
def run(self):
+
if self.node_ready:
t = threading.Thread(target=self.work, args=())
t.start()
- Logger.log(LOG_LEVEL["info"], str(self.config['name']) + ' Node Worker [' + str(
- len(self.config['sensors'])) + ' Sensors]...\t\033[1;32m Online\033[0;0m')
+ Logger.log(
+ LOG_LEVEL["info"],
+ str(self.config['name']) + ' Node Worker [' + str(
+ len(self.config[
+ 'sensors'])) + ' Sensors]...\t\033[1;32m Online\033[0;0m'
+ )
return t
+
else:
- Logger.log(LOG_LEVEL["warning"], "Node Connection...\t\t\t\033[1;31m Failed\033[0;0m")
+ Logger.log(
+ LOG_LEVEL["warning"],
+ "Node Connection...\t\t\t\033[1;31m Failed\033[0;0m"
+ )
return None
def work(self):
@@ -112,5 +130,8 @@ def work(self):
time.sleep(15)
# This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "{name} Node Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m".format(
- **self.config))
+ Logger.log(
+ LOG_LEVEL["info"],
+ "{name} Node Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m".format(
+ **self.config)
+ )
diff --git a/workers/arduino/arduino_control_worker.py b/workers/arduino/arduino_control_worker.py
index a182125..bd07761 100644
--- a/workers/arduino/arduino_control_worker.py
+++ b/workers/arduino/arduino_control_worker.py
@@ -11,96 +11,118 @@
from controls.arduino.switch_control import (SwitchControl)
from controls.arduino.potentiometer_control import (PotentiometerControl)
import sys
-sys.path.append('..')
-import variables
+
+
+import constants
import importlib
from logger.Logger import Logger, LOG_LEVEL
-#r = redis.Redis(host='127.0.0.1', port=6379)
+
+# r = redis.Redis(host='127.0.0.1', port=6379)
class ArduinoControlWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, node_connected, connection=None):
- super().__init__(config, main_thread_running, system_ready)
- self.controls_ready = False
- self.node_connected = node_connected
- self.connection = connection
-
- self.controls = []
-
- if node_connected.is_set():
- self.init()
- self.controls_ready = True
- return
-
-
- def init(self):
- try:
- for control in self.config['controls']:
- if control.get('type', None) is not None:
- # Get the control from the controls folder {control name}_control.{ControlName}Control
- control_type = 'controls.arduino.' + control.get('type').lower() + '_control.' + control.get('type').capitalize() + 'Control'
-
- analog_pin_mode = False if control.get('is_digital', True) else True
-
- imported_control = self.dynamic_import(control_type)
-
- # Define default kwargs for all control types, conditionally include optional variables below if they exist
- control_kwargs = {
- 'name' : control.get('name', None),
- 'pin' : int(control.get('pin')),
- 'connection': self.connection,
- 'key' : control.get('key', None),
- 'analog_pin_mode': analog_pin_mode,
- 'topic': control.get('topic', None)
- }
-
- # optional control variables
- # add conditional control vars here...
-
- new_control = imported_control(**control_kwargs)
-
- new_control.init_control()
- self.controls.append(new_control)
- self.controls_ready = True
- print('{type} Control {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(**control))
- except (SerialManagerError, SocketManagerError, BrokenPipeError, ConnectionResetError, OSError, socket.timeout) as e:
- # Connection error. Reset everything for reconnect
- self.controls_ready = False
- self.node_connected.clear()
- self.controls = []
- return
-
- def run(self):
- t = threading.Thread(target=self.work, args=())
- t.start()
- return t
-
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- if self.node_connected.is_set():
- if self.controls_ready:
- try:
- readings = {}
- for control in self.controls:
- result = control.read()
- readings[control.key] = result
- except (SerialManagerError, SocketManagerError, BrokenPipeError, ConnectionResetError, OSError, socket.timeout) as e:
- Logger.log(LOG_LEVEL["warning"], '\033[1;36m{name}\033[0;0m -> \033[1;33mControls Timeout!\033[0;0m'.format(**self.config))
- self.controls_ready = False
- self.controls = []
- self.node_connected.clear()
- time.sleep(15)
- else:
- # Worker connected but controls not initialized
- self.init()
- else:
- # Node not connected. Wait for reconnect
- self.controls_ready = False
- self.controls = []
- time.sleep(10)
- #Will this nuke the connection?
- time.sleep(0.05)
- #This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "{name} Controls Shutting Down...\t\033[1;32m Complete\033[0;0m".format(**self.config))
\ No newline at end of file
+ def __init__(self, config, main_thread_running, system_ready,
+ node_connected, connection=None):
+ super().__init__(config, main_thread_running, system_ready)
+ self.controls_ready = False
+ self.node_connected = node_connected
+ self.connection = connection
+
+ self.controls = []
+
+ if node_connected.is_set():
+ self.init()
+ self.controls_ready = True
+ return
+
+ def init(self):
+ try:
+ for control in self.config['controls']:
+ if control.get('type', None) is not None:
+ # Get the control from the controls folder
+ # {control name}_control.{ControlName}Control
+ control_type = 'controls.arduino.' + control.get(
+ 'type').lower() + '_control.' + control.get(
+ 'type').capitalize() + 'Control'
+
+ analog_pin_mode = False if control.get('is_digital',
+ True) else True
+
+ imported_control = self.dynamic_import(control_type)
+
+ # Define default kwargs for all control types,
+ # conditionally include optional variables below
+ # if they exist
+ control_kwargs = {
+ 'name': control.get('name', None),
+ 'pin': int(control.get('pin')),
+ 'connection': self.connection,
+ 'key': control.get('key', None),
+ 'analog_pin_mode': analog_pin_mode,
+ 'topic': control.get('topic', None)
+ }
+
+ # optional control variables
+ # add conditional control vars here...
+
+ new_control = imported_control(**control_kwargs)
+
+ new_control.init_control()
+ self.controls.append(new_control)
+ self.controls_ready = True
+ print(
+ '{type} Control {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(
+ **control)
+ )
+ except (SerialManagerError, SocketManagerError, BrokenPipeError,
+ ConnectionResetError, OSError, socket.timeout) as e:
+ # Connection error. Reset everything for reconnect
+ self.controls_ready = False
+ self.node_connected.clear()
+ self.controls = []
+ return
+
+ def run(self):
+ t = threading.Thread(target=self.work, args=())
+ t.start()
+ return t
+
+ def work(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ if self.node_connected.is_set():
+ if self.controls_ready:
+ try:
+ readings = {}
+ for control in self.controls:
+ result = control.read()
+ readings[control.key] = result
+ except (SerialManagerError, SocketManagerError,
+ BrokenPipeError, ConnectionResetError, OSError,
+ socket.timeout) as e:
+ Logger.log(
+ LOG_LEVEL["warning"],
+ '\033[1;36m{name}\033[0;0m -> \033[1;33mControls Timeout!\033[0;0m'.format(
+ **self.config)
+ )
+ self.controls_ready = False
+ self.controls = []
+ self.node_connected.clear()
+ time.sleep(15)
+ else:
+ # Worker connected but controls not initialized
+ self.init()
+ else:
+ # Node not connected. Wait for reconnect
+ self.controls_ready = False
+ self.controls = []
+ time.sleep(10)
+ # Will this nuke the connection?
+ time.sleep(0.05)
+ # This is only ran after the main thread is shut down
+ Logger.log(
+ LOG_LEVEL["info"],
+ "{name} Controls Shutting Down...\t\033[1;32m Complete\033[0;0m".format(
+ **self.config)
+ )
diff --git a/workers/arduino/arduino_relay_worker.py b/workers/arduino/arduino_relay_worker.py
index 35d55cd..0dae496 100644
--- a/workers/arduino/arduino_relay_worker.py
+++ b/workers/arduino/arduino_relay_worker.py
@@ -9,177 +9,207 @@
from nanpy.serialmanager import SerialManagerError
from nanpy.sockconnection import (SocketManager, SocketManagerError)
from .worker import Worker
-sys.path.append('..')
-import variables
+
+
+import constants
from logger.Logger import Logger, LOG_LEVEL
+
class ArduinoRelayWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, relay_available, relay_active, node_connected, connection=None, api=None):
- super().__init__(config, main_thread_running, system_ready)
- self.config['pin'] = int(self.config['pin']) # parse possbile strings to avoid errors
-
- if self.config.get('key', None) is None:
- raise Exception('No "key" Found in Relay Config')
- else:
- self.key = self.config.get('key', '').replace(" ", "_").lower()
-
- if self.config.get('name', None) is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = self.config['name']
-
- # Events
- self.main_thread_running = main_thread_running
- self.system_ready = system_ready
- self.relay_available = relay_available
- self.relay_active = relay_active
- self.node_connected = node_connected
-
- # Dynamic Properties based on config
- self.active = False
- self.relay_ready = False
- self.topic = self.config['topic'].replace(" ", "/").lower() if self.config['topic'] is not None else 'mudpi/relay/'+self.key
-
- # Pubsub Listeners
- self.pubsub = self.r.pubsub()
- self.pubsub.subscribe(**{self.topic: self.handleMessage})
- self.api = api
-
- if self.node_connected.is_set():
- self.init()
- return
-
- def init(self):
- Logger.log(LOG_LEVEL["info"], '{name} Relay Worker {0}...\t\t\033[1;32m Initializing\033[0;0m'.format(self.key))
- self.api = self.api if self.api is not None else ArduinoApi(connection)
- self.pin_state_off = self.api.HIGH if self.config['normally_open'] is not None and self.config['normally_open'] else self.api.LOW
- self.pin_state_on = self.api.LOW if self.config['normally_open'] is not None and self.config['normally_open'] else self.api.HIGH
- self.api.pinMode(self.config['pin'], self.api.OUTPUT)
- # Close the relay by default, we use the pin state we determined based on the config at init
- self.api.digitalWrite(self.config['pin'], self.pin_state_off)
- time.sleep(0.1)
-
- # Feature to restore relay state in case of crash or unexpected shutdown. This will check for last state stored in redis and set relay accordingly
- if(self.config.get('restore_last_known_state', None) is not None and self.config.get('restore_last_known_state', False) is True):
- if(self.r.get(self.key+'_state')):
- self.api.digitalWrite(self.config['pin'], self.pin_state_on)
- Logger.log(LOG_LEVEL["warning"], 'Restoring Relay \033[1;36m{0} On\033[0;0m'.format(self.key))
-
- self.relay_ready = True
- return
-
- def run(self):
- t = threading.Thread(target=self.work, args=())
- t.start()
- Logger.log(LOG_LEVEL["info"], 'Node Relay {0} Worker...\t\t\033[1;32m Online\033[0;0m'.format(self.key))
- return t
-
- def decodeMessageData(self, message):
- if isinstance(message, dict):
- #print('Dict Found')
- return message
- elif isinstance(message.decode('utf-8'), str):
- try:
- temp = json.loads(message.decode('utf-8'))
- #print('Json Found')
- return temp
- except:
- #print('Json Error. Str Found')
- return {'event':'Unknown', 'data':message}
- else:
- #print('Failed to detect type')
- return {'event':'Unknown', 'data':message}
-
- def handleMessage(self, message):
- data = message['data']
- if data is not None:
- decoded_message = self.decodeMessageData(data)
- try:
- if decoded_message['event'] == 'Switch':
- if decoded_message.get('data', None):
- self.relay_active.set()
- elif decoded_message.get('data', None) == 0:
- self.relay_active.clear()
- Logger.log(LOG_LEVEL["info"], 'Switch Relay \033[1;36m{0}\033[0;0m state to \033[1;36m{1}\033[0;0m'.format(self.key, decoded_message['data']))
- elif decoded_message['event'] == 'Toggle':
- state = 'Off' if self.active else 'On'
- if self.relay_active.is_set():
- self.relay_active.clear()
- else:
- self.relay_active.set()
- Logger.log(LOG_LEVEL["info"], 'Toggle Relay \033[1;36m{0} {1} \033[0;0m'.format(self.key, state))
- except:
- Logger.log(LOG_LEVEL["error"], 'Error Decoding Message for Relay {0}'.format(self.key))
-
- def elapsedTime(self):
- self.time_elapsed = time.perf_counter() - self.time_start
- return self.time_elapsed
-
- def resetElapsedTime(self):
- self.time_start = time.perf_counter()
- pass
-
- def turnOn(self):
- #Turn on relay if its available
- if self.relay_available.is_set():
- if not self.active:
- self.api.digitalWrite(self.config['pin'], self.pin_state_on)
- message = {'event':'StateChanged', 'data':1}
- self.r.set(self.key+'_state', 1)
- self.r.publish(self.topic, json.dumps(message))
- self.active = True
- #self.relay_active.set() This is handled by the redis listener now
- self.resetElapsedTime()
-
- def turnOff(self):
- #Turn off volkeye to flip off relay
- if self.relay_available.is_set():
- if self.active:
- self.api.digitalWrite(self.config['pin'], self.pin_state_off)
- message = {'event':'StateChanged', 'data':0}
- self.r.delete(self.key+'_state')
- self.r.publish(self.topic, json.dumps(message))
- #self.relay_active.clear() This is handled by the redis listener now
- self.active = False
- self.resetElapsedTime()
-
- def work(self):
- self.resetElapsedTime()
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- if self.node_connected.is_set():
- if self.relay_ready:
- try:
- self.pubsub.get_message()
- if self.relay_available.is_set():
- if self.relay_active.is_set():
- self.turnOn()
- else:
- self.turnOff()
- else:
- self.turnOff()
- time.sleep(1)
- except e:
- Logger.log(LOG_LEVEL["error"], "Node Relay Worker \033[1;36m{0}\033[0;0m \t\033[1;31m Unexpected Error\033[0;0m".format(self.key))
- Logger.log(LOG_LEVEL["error"], "Exception: {0}".format(e))
- else:
- self.init()
- else:
- # Node offline
- self.relay_ready = False
- time.sleep(5)
-
- else:
- # System not ready relay should be off
- self.turnOff()
- time.sleep(1)
- self.resetElapsedTime()
-
- time.sleep(0.1)
-
-
- # This is only ran after the main thread is shut down
- # Close the pubsub connection
- self.pubsub.close()
- Logger.log(LOG_LEVEL["info"], "Node Relay {0} Shutting Down...\t\033[1;32m Complete\033[0;0m".format(self.key))
+ def __init__(self, config, main_thread_running, system_ready,
+ relay_available, relay_active, node_connected,
+ connection=None, api=None):
+ super().__init__(config, main_thread_running, system_ready)
+ self.config['pin'] = int(
+ self.config['pin']) # parse possbile strings to avoid errors
+
+ if self.config.get('key', None) is None:
+ raise Exception('No "key" Found in Relay Config')
+ else:
+ self.key = self.config.get('key', '').replace(" ", "_").lower()
+
+ if self.config.get('name', None) is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = self.config['name']
+
+ # Events
+ self.main_thread_running = main_thread_running
+ self.system_ready = system_ready
+ self.relay_available = relay_available
+ self.relay_active = relay_active
+ self.node_connected = node_connected
+
+ # Dynamic Properties based on config
+ self.active = False
+ self.relay_ready = False
+ self.topic = self.config['topic'].replace(" ", "/").lower() if \
+ self.config['topic'] is not None else 'mudpi/relay/' + self.key
+
+ # Pubsub Listeners
+ self.pubsub = self.r.pubsub()
+ self.pubsub.subscribe(**{self.topic: self.handle_message})
+ self.api = api
+
+ if self.node_connected.is_set():
+ self.init()
+ return
+
+ def init(self):
+ Logger.log(LOG_LEVEL["info"],
+ '{name} Relay Worker {0}...\t\t\033[1;32m Initializing\033[0;0m'.format(
+ self.key))
+ self.api = self.api if self.api is not None else ArduinoApi(connection)
+ self.pin_state_off = self.api.HIGH if self.config[
+ 'normally_open'] is not None and \
+ self.config[
+ 'normally_open'] else self.api.LOW
+ self.pin_state_on = self.api.LOW if self.config[
+ 'normally_open'] is not None and \
+ self.config[
+ 'normally_open'] else self.api.HIGH
+ self.api.pinMode(self.config['pin'], self.api.OUTPUT)
+ # Close the relay by default, we use the pin state we determined based on the config at init
+ self.api.digitalWrite(self.config['pin'], self.pin_state_off)
+ time.sleep(0.1)
+
+ # Feature to restore relay state in case of crash or unexpected shutdown. This will check for last state stored in redis and set relay accordingly
+ if (self.config.get('restore_last_known_state',
+ None) is not None and self.config.get(
+ 'restore_last_known_state', False) is True):
+ if (self.r.get(self.key + '_state')):
+ self.api.digitalWrite(self.config['pin'], self.pin_state_on)
+ Logger.log(LOG_LEVEL["warning"],
+ 'Restoring Relay \033[1;36m{0} On\033[0;0m'.format(
+ self.key))
+
+ self.relay_ready = True
+ return
+
+ def run(self):
+ t = threading.Thread(target=self.work, args=())
+ t.start()
+ Logger.log(LOG_LEVEL["info"],
+ 'Node Relay {0} Worker...\t\t\033[1;32m Online\033[0;0m'.format(
+ self.key))
+ return t
+
+ def decode_message_data(self, message):
+ if isinstance(message, dict):
+ # print('Dict Found')
+ return message
+ elif isinstance(message.decode('utf-8'), str):
+ try:
+ temp = json.loads(message.decode('utf-8'))
+ # print('Json Found')
+ return temp
+ except:
+ # print('Json Error. Str Found')
+ return {'event': 'Unknown', 'data': message}
+ else:
+ # print('Failed to detect type')
+ return {'event': 'Unknown', 'data': message}
+
+ def handle_message(self, message):
+ data = message['data']
+ if data is not None:
+ decoded_message = self.decode_message_data(data)
+ try:
+ if decoded_message['event'] == 'Switch':
+ if decoded_message.get('data', None):
+ self.relay_active.set()
+ elif decoded_message.get('data', None) == 0:
+ self.relay_active.clear()
+ Logger.log(LOG_LEVEL["info"],
+ 'Switch Relay \033[1;36m{0}\033[0;0m state to \033[1;36m{1}\033[0;0m'.format(
+ self.key, decoded_message['data']))
+ elif decoded_message['event'] == 'Toggle':
+ state = 'Off' if self.active else 'On'
+ if self.relay_active.is_set():
+ self.relay_active.clear()
+ else:
+ self.relay_active.set()
+ Logger.log(LOG_LEVEL["info"],
+ 'Toggle Relay \033[1;36m{0} {1} \033[0;0m'.format(
+ self.key, state))
+ except:
+ Logger.log(LOG_LEVEL["error"],
+ 'Error Decoding Message for Relay {0}'.format(
+ self.key))
+
+ def elapsed_time(self):
+ self.time_elapsed = time.perf_counter() - self.time_start
+ return self.time_elapsed
+
+ def reset_elapsed_time(self):
+ self.time_start = time.perf_counter()
+ pass
+
+ def turn_on(self):
+ # Turn on relay if its available
+ if self.relay_available.is_set():
+ if not self.active:
+ self.api.digitalWrite(self.config['pin'], self.pin_state_on)
+ message = {'event': 'StateChanged', 'data': 1}
+ self.r.set(self.key + '_state', 1)
+ self.r.publish(self.topic, json.dumps(message))
+ self.active = True
+ # self.relay_active.set() This is handled by the redis listener now
+ self.reset_elapsed_time()
+
+ def turn_off(self):
+ # Turn off volkeye to flip off relay
+ if self.relay_available.is_set():
+ if self.active:
+ self.api.digitalWrite(self.config['pin'], self.pin_state_off)
+ message = {'event': 'StateChanged', 'data': 0}
+ self.r.delete(self.key + '_state')
+ self.r.publish(self.topic, json.dumps(message))
+ # self.relay_active.clear() This is handled by the redis listener now
+ self.active = False
+ self.reset_elapsed_time()
+
+ def work(self):
+ self.reset_elapsed_time()
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ if self.node_connected.is_set():
+ if self.relay_ready:
+ try:
+ self.pubsub.get_message()
+ if self.relay_available.is_set():
+ if self.relay_active.is_set():
+ self.turn_on()
+ else:
+ self.turn_off()
+ else:
+ self.turn_off()
+ time.sleep(1)
+ except e:
+ Logger.log(LOG_LEVEL["error"],
+ "Node Relay Worker \033[1;36m{0}\033[0;0m \t\033[1;31m Unexpected Error\033[0;0m".format(
+ self.key))
+ Logger.log(LOG_LEVEL["error"],
+ "Exception: {0}".format(e))
+ else:
+ self.init()
+ else:
+ # Node offline
+ self.relay_ready = False
+ time.sleep(5)
+
+ else:
+ # System not ready relay should be off
+ self.turn_off()
+ time.sleep(1)
+ self.reset_elapsed_time()
+
+ time.sleep(0.1)
+
+ # This is only ran after the main thread is shut down
+ # Close the pubsub connection
+ self.pubsub.close()
+ Logger.log(LOG_LEVEL["info"],
+ "Node Relay {0} Shutting Down...\t\033[1;32m Complete\033[0;0m".format(
+ self.key))
diff --git a/workers/arduino/arduino_sensor_worker.py b/workers/arduino/arduino_sensor_worker.py
index d56d70d..dead8c3 100644
--- a/workers/arduino/arduino_sensor_worker.py
+++ b/workers/arduino/arduino_sensor_worker.py
@@ -14,106 +14,123 @@
from sensors.arduino.humidity_sensor import (HumiditySensor)
from sensors.arduino.temperature_sensor import (TemperatureSensor)
import sys
-sys.path.append('..')
+
+
import importlib
from logger.Logger import Logger, LOG_LEVEL
-#r = redis.Redis(host='127.0.0.1', port=6379)
+
+# r = redis.Redis(host='127.0.0.1', port=6379)
class ArduinoSensorWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, node_connected, connection=None, api=None):
- super().__init__(config, main_thread_running, system_ready)
- self.topic = config.get('topic', 'sensors').replace(" ", "_").lower()
- self.sensors_ready = False
- self.node_connected = node_connected
- self.connection = connection
- self.api = api
- self.sensors = []
- if node_connected.is_set():
- self.init()
- self.sensors_ready = True
- return
-
- def init(self, connection=None):
- Logger.log(LOG_LEVEL["info"], '{name} Sensor Worker...\t\t\033[1;32m Preparing\033[0;0m'.format(**self.config))
- try:
- for sensor in self.config['sensors']:
- if sensor.get('type', None) is not None:
- #Get the sensor from the sensors folder {sensor name}_sensor.{SensorName}Sensor
- sensor_type = 'sensors.arduino.' + sensor.get('type').lower() + '_sensor.' + sensor.get('type').capitalize() + 'Sensor'
-
- #analog_pin_mode = False if sensor.get('is_digital', False) else True
-
- imported_sensor = self.dynamic_import(sensor_type)
- #new_sensor = imported_sensor(sensor.get('pin'), name=sensor.get('name', sensor.get('type')), connection=self.connection, key=sensor.get('key', None))
-
- # Define default kwargs for all sensor types, conditionally include optional variables below if they exist
- sensor_kwargs = {
- 'name' : sensor.get('name', None),
- 'pin' : int(sensor.get('pin')),
- 'connection': self.connection,
- 'key' : sensor.get('key', None)
- }
-
- # optional sensor variables
- # Model is specific to DHT modules to specify DHT11(11) DHT22(22) or DHT2301(21)
- if sensor.get('model'):
- sensor_kwargs['model'] = str(sensor.get('model'))
- sensor_kwargs['api'] = self.api
-
- new_sensor = imported_sensor(**sensor_kwargs)
-
- # print('{type} Sensor {pin}...\t\t\t\033[1;32m Preparing\033[0;0m'.format(**sensor))
- new_sensor.init_sensor()
- self.sensors.append(new_sensor)
- # print('{type} Sensor {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(**sensor))
- self.sensors_ready = True
- except (SerialManagerError, SocketManagerError, BrokenPipeError, ConnectionResetError, OSError, socket.timeout) as e:
- # Connection error. Reset everything for reconnect
- self.sensors_ready = False
- self.node_connected.clear()
- self.sensors = []
- return
-
- def run(self):
- t = threading.Thread(target=self.work, args=())
- t.start()
- Logger.log(LOG_LEVEL["info"], 'Node {name} Sensor Worker...\t\t\033[1;32m Online\033[0;0m'.format(**self.config))
- return t
-
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- if self.node_connected.is_set():
- if self.sensors_ready:
- try:
- message = {'event':'SensorUpdate'}
- readings = {}
- for sensor in self.sensors:
- result = sensor.read()
- readings[sensor.key] = result
- #r.set(sensor.get('key', sensor.get('type')), value)
-
- Logger.log(LOG_LEVEL["debug"], "Node Readings: ", readings)
- message['data'] = readings
- self.r.publish(self.topic, json.dumps(message))
- except (SerialManagerError, SocketManagerError, BrokenPipeError, ConnectionResetError, OSError, socket.timeout) as e:
- Logger.log(LOG_LEVEL["warning"], '\033[1;36m{name}\033[0;0m -> \033[1;33mSensors Timeout!\033[0;0m'.format(**self.config))
- self.sensors = []
- self.node_connected.clear()
- time.sleep(15)
- else:
- # Worker connected but sensors not initialized
- self.init()
- self.sensors_ready = True
- else:
- #Node not connected, sensors not ready. Wait for reconnect
- self.sensors = []
- self.sensors_ready = False
-
- # Main loop delay between cycles
- time.sleep(self.sleep_duration)
-
- #This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "{name} Sensors Shutting Down...\t\033[1;32m Complete\033[0;0m".format(**self.config))
\ No newline at end of file
+ def __init__(self, config, main_thread_running, system_ready,
+ node_connected, connection=None, api=None):
+ super().__init__(config, main_thread_running, system_ready)
+ self.topic = config.get('topic', 'sensors').replace(" ", "_").lower()
+ self.sensors_ready = False
+ self.node_connected = node_connected
+ self.connection = connection
+ self.api = api
+ self.sensors = []
+ if node_connected.is_set():
+ self.init()
+ self.sensors_ready = True
+ return
+
+ def init(self, connection=None):
+ Logger.log(LOG_LEVEL["info"],
+ '{name} Sensor Worker...\t\t\033[1;32m Preparing\033[0;0m'.format(
+ **self.config))
+ try:
+ for sensor in self.config['sensors']:
+ if sensor.get('type', None) is not None:
+ # Get the sensor from the sensors folder {sensor name}_sensor.{SensorName}Sensor
+ sensor_type = 'sensors.arduino.' + sensor.get(
+ 'type').lower() + '_sensor.' + sensor.get(
+ 'type').capitalize() + 'Sensor'
+
+ # analog_pin_mode = False if sensor.get('is_digital', False) else True
+
+ imported_sensor = self.dynamic_import(sensor_type)
+ # new_sensor = imported_sensor(sensor.get('pin'), name=sensor.get('name', sensor.get('type')), connection=self.connection, key=sensor.get('key', None))
+
+ # Define default kwargs for all sensor types, conditionally include optional variables below if they exist
+ sensor_kwargs = {
+ 'name': sensor.get('name', None),
+ 'pin': int(sensor.get('pin')),
+ 'connection': self.connection,
+ 'key': sensor.get('key', None)
+ }
+
+ # optional sensor variables
+ # Model is specific to DHT modules to specify DHT11(11) DHT22(22) or DHT2301(21)
+ if sensor.get('model'):
+ sensor_kwargs['model'] = str(sensor.get('model'))
+ sensor_kwargs['api'] = self.api
+
+ new_sensor = imported_sensor(**sensor_kwargs)
+
+ # print('{type} Sensor {pin}...\t\t\t\033[1;32m Preparing\033[0;0m'.format(**sensor))
+ new_sensor.init_sensor()
+ self.sensors.append(new_sensor)
+ # print('{type} Sensor {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(**sensor))
+ self.sensors_ready = True
+ except (SerialManagerError, SocketManagerError, BrokenPipeError,
+ ConnectionResetError, OSError, socket.timeout) as e:
+ # Connection error. Reset everything for reconnect
+ self.sensors_ready = False
+ self.node_connected.clear()
+ self.sensors = []
+ return
+
+ def run(self):
+ t = threading.Thread(target=self.work, args=())
+ t.start()
+ Logger.log(LOG_LEVEL["info"],
+ 'Node {name} Sensor Worker...\t\t\033[1;32m Online\033[0;0m'.format(
+ **self.config))
+ return t
+
+ def work(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ if self.node_connected.is_set():
+ if self.sensors_ready:
+ try:
+ message = {'event': 'SensorUpdate'}
+ readings = {}
+ for sensor in self.sensors:
+ result = sensor.read()
+ readings[sensor.key] = result
+ # r.set(sensor.get('key', sensor.get('type')), value)
+
+ Logger.log(LOG_LEVEL["debug"],
+ "Node Readings: {0}".format(readings))
+ message['data'] = readings
+ self.r.publish(self.topic, json.dumps(message))
+ except (SerialManagerError, SocketManagerError,
+ BrokenPipeError, ConnectionResetError, OSError,
+ socket.timeout) as e:
+ Logger.log(LOG_LEVEL["warning"],
+ '\033[1;36m{name}\033[0;0m -> \033[1;33mSensors Timeout!\033[0;0m'.format(
+ **self.config))
+ self.sensors = []
+ self.node_connected.clear()
+ time.sleep(15)
+ else:
+ # Worker connected but sensors not initialized
+ self.init()
+ self.sensors_ready = True
+ else:
+ # Node not connected, sensors not ready. Wait for reconnect
+ self.sensors = []
+ self.sensors_ready = False
+
+ # Main loop delay between cycles
+ time.sleep(self.sleep_duration)
+
+ # This is only ran after the main thread is shut down
+ Logger.log(LOG_LEVEL["info"],
+ "{name} Sensors Shutting Down...\t\033[1;32m Complete\033[0;0m".format(
+ **self.config))
diff --git a/workers/arduino/arduino_worker.py b/workers/arduino/arduino_worker.py
index 0e08445..7b193d3 100644
--- a/workers/arduino/arduino_worker.py
+++ b/workers/arduino/arduino_worker.py
@@ -11,177 +11,234 @@
from workers.arduino.arduino_relay_worker import ArduinoRelayWorker
from .worker import Worker
import sys
-sys.path.append('..')
+
+
import importlib
from logger.Logger import Logger, LOG_LEVEL
-#r = redis.Redis(host='127.0.0.1', port=6379)
+
+# r = redis.Redis(host='127.0.0.1', port=6379)
class ArduinoWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, connection=None):
- super().__init__(config, main_thread_running, system_ready)
- self.connection = connection
- self.threads = []
-
- # Events
- self.node_ready = threading.Event()
- self.node_connected = threading.Event() # Event to signal if node can be used
-
- self.workers = []
- self.relays = []
- self.relayEvents = {}
- self.relay_index = 0
-
- if connection is None:
- self.connection = self.connect()
-
- try:
- if self.config['controls'] is not None:
- acw = ArduinoControlWorker(self.config, main_thread_running, system_ready, self.node_connected, self.connection)
- self.workers.append(acw)
- time.sleep(3)
- except KeyError:
- Logger.log(LOG_LEVEL["info"], '{name} Node Controls...\t\t\033[1;31m Disabled\033[0;0m'.format(**self.config))
-
- try:
- if self.config['relays'] is not None:
- for relay in self.config['relays']:
- #Create a threading event for each relay to check status
- relayState = {
- "available": threading.Event(), #Event to allow relay to activate
- "active": threading.Event() #Event to signal relay to open/close
- }
- # Store the relays under the key or index if no key is found, this way we can reference the right relays
- self.relayEvents[relay.get("key", self.relay_index)] = relayState
- # Create sensor worker for a relay
- arw = ArduinoRelayWorker(relay, main_thread_running, system_ready, relayState['available'], relayState['active'], self.node_connected, self.connection, self.api)
- # Make the relays available, this event is toggled off elsewhere if we need to disable relays
- relayState['available'].set()
- self.relay_index +=1
- self.workers.append(arw)
- time.sleep(3)
- except KeyError:
- Logger.log(LOG_LEVEL["info"], '{name} Node Relays...\t\t\033[1;31m Disabled\033[0;0m'.format(**self.config))
-
- try:
- if self.config['sensors'] is not None:
- asw = ArduinoSensorWorker(self.config, main_thread_running, system_ready, self.node_connected, self.connection, self.api)
- self.workers.append(asw)
- time.sleep(3)
- except KeyError:
- Logger.log(LOG_LEVEL["info"], '{name} Node Sensors...\t\t\033[1;31m Disabled\033[0;0m'.format(**self.config))
-
- return
-
- def connect(self):
- attempts = 3
- conn = None
- if self.config.get('use_wifi', False):
- while attempts > 0 and self.main_thread_running.is_set():
- try:
- Logger.log(LOG_LEVEL["debug"], '\033[1;36m{0}\033[0;0m -> Connecting... \t'.format(self.config["name"], (3-attempts)))
- attempts-= 1
- conn = SocketManager(host=str(self.config.get('address', 'mudpi.local')))
- # Test the connection with api
- self.api = ArduinoApi(connection=conn)
- except (SocketManagerError, BrokenPipeError, ConnectionResetError, socket.timeout) as e:
- Logger.log(LOG_LEVEL["warning"], '{name} -> Connecting...\t\t\033[1;33m Timeout\033[0;0m '.format(**self.config))
- if attempts > 0:
- Logger.log(LOG_LEVEL["info"], '{name} -> Preparing Reconnect... \t'.format(**self.config))
- else:
- Logger.log(LOG_LEVEL["error"], '{name} -> Connection Attempts...\t\033[1;31m Failed\033[0;0m '.format(**self.config))
- conn = None
- self.resetConnection()
- time.sleep(15)
- except (OSError, KeyError) as e:
- Logger.log(LOG_LEVEL["error"], '[{name}] \033[1;33m Node Not Found. (Is it online?)\033[0;0m'.format(**self.config))
- conn = None
- self.resetConnection()
- time.sleep(15)
- else:
- Logger.log(LOG_LEVEL["info"], '{name} -> Wifi Connection \t\t\033[1;32m Success\033[0;0m '.format(**self.config))
- for worker in self.workers:
- worker.connection = conn
- self.node_connected.set()
- self.node_ready.set()
- break
- else:
- while attempts > 0 and self.main_thread_running.is_set():
- try:
- attempts-= 1
- conn = SerialManager(device=str(self.config.get('address', '/dev/ttyUSB1')))
- except SerialManagerError:
- Logger.log(LOG_LEVEL["warning"], '{name} -> Connecting...\t\t\033[1;33m Timeout\033[0;0m '.format(**self.config))
- if attempts > 0:
- Logger.log(LOG_LEVEL["info"], '{name} -> Preparing Reconnect... \t'.format(**self.config), end='\r', flush=True)
- else:
- Logger.log(LOG_LEVEL["error"], '{name} -> Connection Attempts...\t\033[1;31m Failed\033[0;0m '.format(**self.config))
- self.resetConnection()
- conn = None
- time.sleep(15)
- else:
- if conn is not None:
- Logger.log(LOG_LEVEL["info"], '[{name}] Serial Connection \t\033[1;32m Success\033[0;0m '.format(**self.config))
- for worker in self.workers:
- worker.connection = conn
- self.node_connected.set()
- self.node_ready.set()
- break
- return conn
-
- def resetConnection(self):
- self.connection = None
- self.node_connected.clear()
- self.node_ready.clear()
-
-
- def run(self):
- for worker in self.workers:
- t = worker.run()
- self.threads.append(t)
- time.sleep(1)
-
- t = threading.Thread(target=self.work, args=())
- t.start()
- if self.node_ready.is_set():
- Logger.log(LOG_LEVEL["info"], str(self.config['name']) +' Node Worker '+ '[S: ' + str(len(self.config.get('sensors', []))) + ']' + '[C: ' + str(len(self.config.get('controls', []))) + ']...\t\033[1;32m Online\033[0;0m')
- else:
- Logger.log(LOG_LEVEL["info"], str(self.config['name']) +'...\t\t\t\t\033[1;33m Pending Reconnect\033[0;0m ')
- return t
-
- def work(self):
- delay_multiplier = 1
- while self.main_thread_running.is_set():
- if self.system_ready.is_set() and self.node_ready.is_set():
- if not self.node_connected.is_set():
- #Connection Broken - Reset Connection
- self.resetConnection()
- Logger.log(LOG_LEVEL["warning"], '\033[1;36m{name}\033[0;0m -> \033[1;33mTimeout!\033[0;0m \t\t\t\033[1;31m Connection Broken\033[0;0m'.format(**self.config))
- time.sleep(30)
- else:
- # Node reconnection cycle
- if not self.node_connected.is_set():
- # Random delay before connections to offset multiple attempts (1-5 min delay)
- random_delay = (random.randrange(30, self.config.get("max_reconnect_delay", 300)) * delay_multiplier) / 2
- time.sleep(10)
- Logger.log(LOG_LEVEL["info"], '\033[1;36m'+str(self.config['name']) +'\033[0;0m -> Retrying in '+ '{0}s...'.format(random_delay)+'\t\033[1;33m Pending Reconnect\033[0;0m ')
- # Two separate checks for main thread event to prevent re-connections during shutdown
- if self.main_thread_running.is_set():
- time.sleep(random_delay)
- if self.main_thread_running.is_set():
- self.connection = self.connect()
- if self.connection is None:
- delay_multiplier += 1
- if delay_multiplier > 6:
- delay_multiplier = 6
- else:
- delay_multiplier = 1
- # Main loop delay between cycles
- time.sleep(self.sleep_duration)
-
- # This is only ran after the main thread is shut down
- # Join all our sub threads for shutdown
- for thread in self.threads:
- thread.join()
- Logger.log(LOG_LEVEL["info"], ("{name} Shutting Down...\t\t\033[1;32m Complete\033[0;0m".format(**self.config)))
\ No newline at end of file
+ def __init__(self, config, main_thread_running, system_ready,
+ connection=None):
+ super().__init__(config, main_thread_running, system_ready)
+ self.connection = connection
+ self.threads = []
+
+ # Events
+ self.node_ready = threading.Event()
+ self.node_connected = threading.Event() # Event to signal if node can be used
+
+ self.workers = []
+ self.relays = []
+ self.relayEvents = {}
+ self.relay_index = 0
+
+ if connection is None:
+ self.connection = self.connect()
+
+ try:
+ if self.config['controls'] is not None:
+ acw = ArduinoControlWorker(self.config, main_thread_running,
+ system_ready, self.node_connected,
+ self.connection)
+ self.workers.append(acw)
+ time.sleep(3)
+ except KeyError:
+ Logger.log(LOG_LEVEL["info"],
+ '{name} Node Controls...\t\t\033[1;31m Disabled\033[0;0m'.format(
+ **self.config))
+
+ try:
+ if self.config['relays'] is not None:
+ for relay in self.config['relays']:
+ # Create a threading event for each relay to check status
+ relayState = {
+ "available": threading.Event(),
+ # Event to allow relay to activate
+ "active": threading.Event()
+ # Event to signal relay to open/close
+ }
+ # Store the relays under the key or index if no key is found, this way we can reference the right relays
+ self.relayEvents[
+ relay.get("key", self.relay_index)] = relayState
+ # Create sensor worker for a relay
+ arw = ArduinoRelayWorker(relay, main_thread_running,
+ system_ready,
+ relayState['available'],
+ relayState['active'],
+ self.node_connected,
+ self.connection, self.api)
+ # Make the relays available, this event is toggled off elsewhere if we need to disable relays
+ relayState['available'].set()
+ self.relay_index += 1
+ self.workers.append(arw)
+ time.sleep(3)
+ except KeyError:
+ Logger.log(LOG_LEVEL["info"],
+ '{name} Node Relays...\t\t\033[1;31m Disabled\033[0;0m'.format(
+ **self.config))
+
+ try:
+ if self.config['sensors'] is not None:
+ asw = ArduinoSensorWorker(self.config, main_thread_running,
+ system_ready, self.node_connected,
+ self.connection, self.api)
+ self.workers.append(asw)
+ time.sleep(3)
+ except KeyError:
+ Logger.log(LOG_LEVEL["info"],
+ '{name} Node Sensors...\t\t\033[1;31m Disabled\033[0;0m'.format(
+ **self.config))
+
+ return
+
+ def connect(self):
+ attempts = 3
+ conn = None
+ if self.config.get('use_wifi', False):
+ while attempts > 0 and self.main_thread_running.is_set():
+ try:
+ Logger.log(LOG_LEVEL["debug"],
+ '\033[1;36m{0}\033[0;0m -> Connecting... \t'.format(
+ self.config["name"], (3 - attempts)))
+ attempts -= 1
+ conn = SocketManager(
+ host=str(self.config.get('address', 'mudpi.local')))
+ # Test the connection with api
+ self.api = ArduinoApi(connection=conn)
+ except (
+ SocketManagerError, BrokenPipeError, ConnectionResetError,
+ socket.timeout) as e:
+ Logger.log(LOG_LEVEL["warning"],
+ '{name} -> Connecting...\t\t\033[1;33m Timeout\033[0;0m '.format(
+ **self.config))
+ if attempts > 0:
+ Logger.log(LOG_LEVEL["info"],
+ '{name} -> Preparing Reconnect... \t'.format(
+ **self.config))
+ else:
+ Logger.log(LOG_LEVEL["error"],
+ '{name} -> Connection Attempts...\t\033[1;31m Failed\033[0;0m '.format(
+ **self.config))
+ conn = None
+ self.resetConnection()
+ time.sleep(15)
+ except (OSError, KeyError) as e:
+ Logger.log(LOG_LEVEL["error"],
+ '[{name}] \033[1;33m Node Not Found. (Is it online?)\033[0;0m'.format(
+ **self.config))
+ conn = None
+ self.resetConnection()
+ time.sleep(15)
+ else:
+ Logger.log(LOG_LEVEL["info"],
+ '{name} -> Wifi Connection \t\t\033[1;32m Success\033[0;0m '.format(
+ **self.config))
+ for worker in self.workers:
+ worker.connection = conn
+ self.node_connected.set()
+ self.node_ready.set()
+ break
+ else:
+ while attempts > 0 and self.main_thread_running.is_set():
+ try:
+ attempts -= 1
+ conn = SerialManager(
+ device=str(self.config.get('address', '/dev/ttyUSB1')))
+ except SerialManagerError:
+ Logger.log(LOG_LEVEL["warning"],
+ '{name} -> Connecting...\t\t\033[1;33m Timeout\033[0;0m '.format(
+ **self.config))
+ if attempts > 0:
+ Logger.log(LOG_LEVEL["info"],
+ '{name} -> Preparing Reconnect... \t'.format(
+ **self.config), end='\r', flush=True)
+ else:
+ Logger.log(LOG_LEVEL["error"],
+ '{name} -> Connection Attempts...\t\033[1;31m Failed\033[0;0m '.format(
+ **self.config))
+ self.resetConnection()
+ conn = None
+ time.sleep(15)
+ else:
+ if conn is not None:
+ Logger.log(LOG_LEVEL["info"],
+ '[{name}] Serial Connection \t\033[1;32m Success\033[0;0m '.format(
+ **self.config))
+ for worker in self.workers:
+ worker.connection = conn
+ self.node_connected.set()
+ self.node_ready.set()
+ break
+ return conn
+
+ def resetConnection(self):
+ self.connection = None
+ self.node_connected.clear()
+ self.node_ready.clear()
+
+ def run(self):
+ for worker in self.workers:
+ t = worker.run()
+ self.threads.append(t)
+ time.sleep(1)
+
+ t = threading.Thread(target=self.work, args=())
+ t.start()
+ if self.node_ready.is_set():
+ Logger.log(LOG_LEVEL["info"], str(
+ self.config['name']) + ' Node Worker ' + '[S: ' + str(
+ len(self.config.get('sensors', []))) + ']' + '[C: ' + str(len(
+ self.config.get('controls',
+ []))) + ']...\t\033[1;32m Online\033[0;0m')
+ else:
+ Logger.log(LOG_LEVEL["info"], str(self.config[
+ 'name']) + '...\t\t\t\t\033[1;33m Pending Reconnect\033[0;0m ')
+ return t
+
+ def work(self):
+ delay_multiplier = 1
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set() and self.node_ready.is_set():
+ if not self.node_connected.is_set():
+ # Connection Broken - Reset Connection
+ self.resetConnection()
+ Logger.log(LOG_LEVEL["warning"],
+ '\033[1;36m{name}\033[0;0m -> \033[1;33mTimeout!\033[0;0m \t\t\t\033[1;31m Connection Broken\033[0;0m'.format(
+ **self.config))
+ time.sleep(30)
+ else:
+ # Node reconnection cycle
+ if not self.node_connected.is_set():
+ # Random delay before connections to offset multiple attempts (1-5 min delay)
+ random_delay = (random.randrange(30, self.config.get(
+ "max_reconnect_delay", 300)) * delay_multiplier) / 2
+ time.sleep(10)
+ Logger.log(LOG_LEVEL["info"], '\033[1;36m' + str(
+ self.config[
+ 'name']) + '\033[0;0m -> Retrying in ' + '{0}s...'.format(
+ random_delay) + '\t\033[1;33m Pending Reconnect\033[0;0m ')
+ # Two separate checks for main thread event to prevent re-connections during shutdown
+ if self.main_thread_running.is_set():
+ time.sleep(random_delay)
+ if self.main_thread_running.is_set():
+ self.connection = self.connect()
+ if self.connection is None:
+ delay_multiplier += 1
+ if delay_multiplier > 6:
+ delay_multiplier = 6
+ else:
+ delay_multiplier = 1
+ # Main loop delay between cycles
+ time.sleep(self.sleep_duration)
+
+ # This is only ran after the main thread is shut down
+ # Join all our sub threads for shutdown
+ for thread in self.threads:
+ thread.join()
+ Logger.log(LOG_LEVEL["info"], (
+ "{name} Shutting Down...\t\t\033[1;32m Complete\033[0;0m".format(
+ **self.config)))
diff --git a/workers/arduino/worker.py b/workers/arduino/worker.py
index b0796e6..9d0125e 100644
--- a/workers/arduino/worker.py
+++ b/workers/arduino/worker.py
@@ -4,69 +4,72 @@
import redis
import threading
import sys
-sys.path.append('..')
+
+
from logger.Logger import Logger, LOG_LEVEL
+
# Base Worker Class
# A worker is responsible for handling its set of operations and running on a thread
class Worker():
- def __init__(self, config, main_thread_running, system_ready):
- self.config = config
- try:
- self.r = config["redis"]
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- self.topic = config.get('topic', 'mudpi').replace(" ", "_").lower()
- self.sleep_duration = config.get('sleep_duration', 15)
+ def __init__(self, config, main_thread_running, system_ready):
+ self.config = config
+ try:
+ self.r = config["redis"]
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+ self.topic = config.get('topic', 'mudpi').replace(" ", "_").lower()
+ self.sleep_duration = config.get('sleep_duration', 15)
- # Threading Events to Keep Everything in Sync
- self.main_thread_running = main_thread_running
- self.system_ready = system_ready
- self.worker_available = threading.Event()
+ # Threading Events to Keep Everything in Sync
+ self.main_thread_running = main_thread_running
+ self.system_ready = system_ready
+ self.worker_available = threading.Event()
- self.api = None
- self.components = []
- return
+ self.api = None
+ self.components = []
+ return
- def init(self):
- # print('Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(**control))
- return
+ def init(self):
+ # print('Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(**control))
+ return
- def run(self):
- t = threading.Thread(target=self.work, args=())
- t.start()
- return t
+ def run(self):
+ t = threading.Thread(target=self.work, args=())
+ t.start()
+ return t
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- time.sleep(self.sleep_duration)
- #This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
+ def work(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ time.sleep(self.sleep_duration)
+ # This is only ran after the main thread is shut down
+ Logger.log(LOG_LEVEL["info"],
+ "Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
- def dynamic_import(self, name):
- # Split path of the class folder structure: {sensor name}_sensor . {SensorName}Sensor
- components = name.split('.')
- # Dynamically import root of component path
- module = __import__(components[0])
- # Get component attributes
- for component in components[1:]:
- module = getattr(module, component)
- return module
+ def dynamic_import(self, name):
+ # Split path of the class folder structure: {sensor name}_sensor . {SensorName}Sensor
+ components = name.split('.')
+ # Dynamically import root of component path
+ module = __import__(components[0])
+ # Get component attributes
+ for component in components[1:]:
+ module = getattr(module, component)
+ return module
- def decodeMessageData(self, message):
- if isinstance(message, dict):
- #print('Dict Found')
- return message
- elif isinstance(message.decode('utf-8'), str):
- try:
- temp = json.loads(message.decode('utf-8'))
- #print('Json Found')
- return temp
- except:
- #print('Json Error. Str Found')
- return {'event':'Unknown', 'data':message}
- else:
- #print('Failed to detect type')
- return {'event':'Unknown', 'data':message}
\ No newline at end of file
+ def decode_message_data(self, message):
+ if isinstance(message, dict):
+ # print('Dict Found')
+ return message
+ elif isinstance(message.decode('utf-8'), str):
+ try:
+ temp = json.loads(message.decode('utf-8'))
+ # print('Json Found')
+ return temp
+ except:
+ # print('Json Error. Str Found')
+ return {'event': 'Unknown', 'data': message}
+ else:
+ # print('Failed to detect type')
+ return {'event': 'Unknown', 'data': message}
diff --git a/workers/pi/__init__.py b/workers/linux/__init__.py
similarity index 100%
rename from workers/pi/__init__.py
rename to workers/linux/__init__.py
diff --git a/workers/linux/camera_worker.py b/workers/linux/camera_worker.py
new file mode 100644
index 0000000..04ce4cd
--- /dev/null
+++ b/workers/linux/camera_worker.py
@@ -0,0 +1,208 @@
+"""
+This is (for instance) a Raspberry Pi only worker!
+
+
+The libcamera project (in development), aims to offer an open source stack for
+cameras for Linux, ChromeOS and Android.
+It will be able to detect and manage all of the exposed camera on the system.
+Connected via USB or CSI (Rasperry pi camera).
+libcamera developers plan to privide Python bindings:
+https://www.raspberrypi.org/blog/an-open-source-camera-stack-for-raspberry-pi-using-libcamera/#comment-1528789
+
+Not available at time of writing: 9 Nov 2020
+
+Once available, we should look forward migrating to this library, as it would
+allow our worker to support multiple boards and devices.
+"""
+
+import datetime
+import json
+import os
+import sys
+import threading
+import time
+
+from picamera import PiCamera
+from utils import get_config_item
+
+from workers.linux.worker import Worker
+
+
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+class CameraWorker(Worker):
+ def __init__(self, config, main_thread_running, system_ready,
+ camera_available):
+ super().__init__(config, main_thread_running, system_ready)
+ self.pending_reset = False
+
+ # Events
+ self.camera_available = camera_available
+
+ # Dynamic Properties based on config
+ self.path = get_config_item(self.config, 'path', '/etc/mudpi/img/')
+ self.topic = get_config_item(
+ self.config, 'topic', 'mudpi/camera/', replace_char="/"
+ )
+
+ if self.config['resolution'] is not None:
+ self.resolutionX = int(self.config['resolution'].get('x', 1920))
+ self.resolutionY = int(self.config['resolution'].get('y', 1080))
+ if self.config['delay'] is not None:
+ self.hours = int(self.config['delay'].get('hours', 0))
+ self.minutes = int(self.config['delay'].get('minutes', 0))
+ self.seconds = int(self.config['delay'].get('seconds', 0))
+
+ self.init()
+ return
+
+ def init(self):
+ try:
+ self.camera = PiCamera(
+ resolution=(self.resolutionX, self.resolutionY))
+ # Below we calibrate the camera for consistent imaging
+ self.camera.framerate = 30
+ # Wait for the automatic gain control to settle
+ time.sleep(2)
+ # Now fix the values
+ self.camera.shutter_speed = self.camera.exposure_speed
+ self.camera.exposure_mode = 'off'
+ g = self.camera.awb_gains
+ self.camera.awb_mode = 'off'
+ self.camera.awb_gains = g
+ except Exception:
+ self.camera = PiCamera()
+
+ # Pubsub Listeners
+ self.pubsub = self.r.pubsub()
+ self.pubsub.subscribe(**{self.topic: self.handle_event})
+
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Camera Worker...\t\t\t\033[1;32m Ready\033[0;0m'
+ )
+ return
+
+ def run(self):
+ thread = threading.Thread(target=self.work, args=())
+ thread.start()
+ self.listener = threading.Thread(target=self.listen, args=())
+ self.listener.start()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Camera Worker...\t\t\t\033[1;32m Online\033[0;0m'
+ )
+ return thread
+
+ def wait(self):
+ # Calculate the delay
+ try:
+ self.next_time = (datetime.datetime.now() + datetime.timedelta(
+ hours=self.hours, minutes=self.minutes,
+ seconds=self.seconds)).replace(microsecond=0)
+ except Exception:
+ # Default every hour
+ self.next_time = (
+ datetime.datetime.now() + datetime.timedelta(hours=1)
+ ).replace(minute=0, second=0, microsecond=0)
+ delay = (self.next_time - datetime.datetime.now()).seconds
+ time.sleep(delay)
+
+ def handle_event(self, message):
+ data = message['data']
+ decoded_message = None
+
+ if data is not None:
+
+ try:
+ if isinstance(data, dict):
+ decoded_message = data
+
+ elif isinstance(data.decode('utf-8'), str):
+ temp = json.loads(data.decode('utf-8'))
+ decoded_message = temp
+ if decoded_message['event'] == 'Timelapse':
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Camera Signaled for Reset"
+ )
+ self.camera_available.clear()
+ self.pending_reset = True
+ except Exception:
+ Logger.log(LOG_LEVEL["error"],
+ 'Error Handling Event for Camera')
+
+ def listen(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ if self.camera_available.is_set():
+ self.pubsub.get_message()
+ time.sleep(1)
+ else:
+ delay = (
+ self.next_time - datetime.datetime.now()
+ ).seconds + 15
+ # wait 15 seconds after next scheduled picture
+ time.sleep(delay)
+ self.camera_available.set()
+ else:
+ time.sleep(2)
+ return
+
+ def work(self):
+ self.reset_elapsed_time()
+
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+
+ if self.camera_available.is_set():
+ # try:
+ for i, filename in enumerate(
+ self.camera.capture_continuous(
+ self.path + 'mudpi-{counter:05d}.jpg')):
+
+ if not self.camera_available.is_set():
+ if self.pending_reset:
+ try:
+ # cleanup previous file
+ os.remove(
+ filename
+ )
+ self.pending_reset = False
+ except Exception:
+ Logger.log(
+ LOG_LEVEL["error"],
+ "Error During Camera Reset Cleanup"
+ )
+ break
+ message = {'event': 'StateChanged', 'data': filename}
+ self.r.set('last_camera_image', filename)
+ self.r.publish(self.topic, json.dumps(message))
+ Logger.log(
+ LOG_LEVEL["debug"],
+ 'Image Captured \033[1;36m%s\033[0;0m' % filename
+ )
+ self.wait()
+ # except:
+ # print("Camera Worker \t\033[1;31m Unexpected Error\033[0;0m")
+ # time.sleep(30)
+ else:
+ time.sleep(1)
+ self.reset_elapsed_time()
+ else:
+ # System not ready camera should be off
+ time.sleep(1)
+ self.reset_elapsed_time()
+
+ time.sleep(0.1)
+
+ # This is only ran after the main thread is shut down
+ self.camera.close()
+ self.listener.join()
+ self.pubsub.close()
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Camera Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m"
+ )
diff --git a/workers/linux/control_worker.py b/workers/linux/control_worker.py
new file mode 100644
index 0000000..adcabbe
--- /dev/null
+++ b/workers/linux/control_worker.py
@@ -0,0 +1,81 @@
+import sys
+import time
+
+from utils import get_config_item
+from workers.linux.worker import Worker
+
+
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+class PiControlWorker(Worker):
+ def __init__(self, config, main_thread_running, system_ready):
+ super().__init__(config, main_thread_running, system_ready)
+ self.topic = get_config_item(self.config, 'topic', 'controls')
+ self.sleep_duration = config.get('sleep_duration', 0.5)
+
+ self.controls = []
+ self.init()
+ return
+
+ def init(self):
+ for control in self.config['controls']:
+ if control.get('type', None) is not None:
+ # Get the control from the controls folder
+ # {control name}_control.{ControlName}Control
+ control_type = 'controls.linux.'
+ control_type += control.get('type').lower()
+ control_type += '_control.'
+ control_type += control.get('type').capitalize() + 'Control'
+
+ imported_control = self.dynamic_import(control_type)
+
+ # Define default kwargs for all control types,
+ # conditionally include optional variables below if they exist
+ control_kwargs = {
+ 'name': control.get('name', None),
+ 'pin': int(control.get('pin')),
+ 'key': control.get('key', None),
+ 'topic': control.get('topic', None),
+ 'resistor': control.get('resistor', None),
+ 'edge_detection': control.get('edge_detection', None),
+ 'debounce': control.get('debounce', None)
+ }
+
+ # optional control variables
+ # add conditional control vars here...
+
+ new_control = imported_control(**control_kwargs)
+
+ new_control.init_control()
+ self.controls.append(new_control)
+ Logger.log(
+ LOG_LEVEL["info"],
+ '{type} Control {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(
+ **control)
+ )
+ return
+
+ def run(self):
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Pi Control Worker [' + str(
+ len(self.config['controls'])
+ ) + ' Controls]...\t\033[1;32m Online\033[0;0m'
+ )
+ return super().run()
+
+ def work(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ readings = {}
+ for control in self.controls:
+ result = control.read()
+ readings[control.key] = result
+ time.sleep(self.sleep_duration)
+ # This is only ran after the main thread is shut down
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Pi Control Worker Shutting Down...\t\033[1;32m Complete\033[0;0m"
+ )
diff --git a/workers/linux/i2c_worker.py b/workers/linux/i2c_worker.py
new file mode 100644
index 0000000..747c76c
--- /dev/null
+++ b/workers/linux/i2c_worker.py
@@ -0,0 +1,87 @@
+import json
+import sys
+import time
+
+
+from .worker import Worker
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+class PiI2CWorker(Worker):
+ def __init__(self, config, main_thread_running, system_ready):
+ super().__init__(config, main_thread_running, system_ready)
+ self.topic = config.get('topic', 'i2c').replace(" ", "_").lower()
+ self.sleep_duration = config.get('sleep_duration', 30)
+
+ self.sensors = []
+ self.init()
+ return
+
+ def init(self):
+ for sensor in self.config['sensors']:
+
+ if sensor.get('type', None) is not None:
+ # Get the sensor from the sensors folder
+ # {sensor name}_sensor.{SensorName}Sensor
+ sensor_type = 'sensors.pi.i2c.'
+ sensor_type += sensor.get('type').lower()
+ sensor_type += '_sensor.'
+ sensor_type += sensor.get('type').capitalize() + 'Sensor'
+
+ imported_sensor = self.dynamic_import(sensor_type)
+
+ # Define default kwargs for all sensor types,
+ # conditionally include optional variables below if they exist
+ sensor_kwargs = {
+ 'name': sensor.get('name', None),
+ 'address': int(sensor.get('address', 00)),
+ 'key': sensor.get('key', None)
+ }
+
+ # Optional sensor variables
+ # Model is specific to DHT modules to specify
+ # DHT11 DHT22 or DHT2302
+ if sensor.get('model'):
+ sensor_kwargs['model'] = str(sensor.get('model'))
+
+ new_sensor = imported_sensor(**sensor_kwargs)
+ new_sensor.init_sensor()
+
+ # Set the sensor type and determine if the
+ # readings are critical to operations
+ new_sensor.type = sensor.get('type').lower()
+
+ self.sensors.append(new_sensor)
+ # print('{type} Sensor (Pi)
+ # {address}...\t\t\033[1;32m Ready\033[0;0m'.format(**sensor))
+ return
+
+ def run(self):
+ Logger.log(LOG_LEVEL["info"], 'Pi I2C Sensor Worker [' + str(
+ len(self.sensors)) + ' Sensors]...\t\033[1;32m Online\033[0;0m')
+ return super().run()
+
+ def work(self):
+ readings = {}
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+
+ message = {'event': 'PiSensorUpdate'}
+
+ for sensor in self.sensors:
+ result = sensor.read()
+ readings[sensor.key] = result
+ self.r.set(sensor.key, json.dumps(result))
+
+ message['data'] = readings
+ Logger.log(LOG_LEVEL["debug"], str(readings))
+ self.r.publish(self.topic, json.dumps(message))
+ time.sleep(self.sleep_duration)
+
+ time.sleep(2)
+ # This is only ran after the main thread is shut down
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Pi I2C Sensor Worker Shutting Down...\t\033[1;32m Complete\033[0;0m"
+ )
diff --git a/workers/linux/lcd_worker.py b/workers/linux/lcd_worker.py
new file mode 100644
index 0000000..9a65bc1
--- /dev/null
+++ b/workers/linux/lcd_worker.py
@@ -0,0 +1,260 @@
+import re
+import time
+import json
+import redis
+import board
+import busio
+import datetime
+import threading
+import adafruit_character_lcd.character_lcd_rgb_i2c as character_rgb_lcd
+import adafruit_character_lcd.character_lcd_i2c as character_lcd
+from .worker import Worker
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+class LcdWorker(Worker):
+ def __init__(self, config, main_thread_running, system_ready,
+ lcd_available):
+ super().__init__(config, main_thread_running, system_ready)
+ try:
+ self.address = int(self.config['address']) if self.config[
+ 'address'] is not None else None
+ except KeyError:
+ self.address = None
+
+ try:
+ self.model = str(self.config['model']) if self.config[
+ 'model'] is not None else ''
+ except KeyError:
+ self.model = ''
+
+ try:
+ self.columns = int(self.config['columns']) if self.config[
+ 'columns'] is not None else 16
+ except KeyError:
+ self.columns = 16
+
+ try:
+ self.rows = int(self.config['rows']) if self.config[
+ 'rows'] is not None else 2
+ except KeyError:
+ self.rows = 2
+
+ try:
+ self.default_duration = int(self.config['default_duration']) if \
+ self.config['default_duration'] is not None else 5
+ except KeyError:
+ self.default_duration = 5
+
+ self.current_message = ""
+ self.cached_message = {
+ 'message': '',
+ 'duration': self.default_duration
+ }
+ self.need_new_message = True
+ self.message_queue = []
+ self.message_limit = int(
+ self.config['message_limit']) if self.config.get('message_limit',
+ None) is not None else 20
+
+ # Events
+ self.lcd_available = lcd_available
+
+ # Dynamic Properties based on config
+ try:
+ self.topic = self.config['topic'].replace(" ", "/").lower() if \
+ self.config['topic'] is not None else 'mudpi/lcd'
+ except KeyError:
+ self.topic = 'mudpi/lcd'
+
+ # Pubsub Listeners
+ self.pubsub = self.r.pubsub()
+ self.pubsub.subscribe(**{self.topic: self.handle_message})
+
+ self.init()
+ return
+
+ def init(self):
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'LCD Display Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(
+ **self.config)
+ )
+ # prepare sensor on specified pin
+ self.i2c = busio.I2C(board.SCL, board.SDA)
+
+ if (self.model):
+
+ if (self.model.lower() == 'rgb'):
+ self.lcd = character_lcd.Character_LCD_RGB_I2C(
+ self.i2c,
+ self.columns,
+ self.rows,
+ self.address
+ )
+
+ elif (self.model.lower() == 'pcf'):
+ self.lcd = character_lcd.Character_LCD_I2C(
+ self.i2c,
+ self.columns,
+ self.rows,
+ address=self.address,
+ usingPCF=True
+ )
+ else:
+ self.lcd = character_lcd.Character_LCD_I2C(
+ self.i2c,
+ self.columns,
+ self.rows,
+ self.address
+ )
+ else:
+ self.lcd = character_lcd.Character_LCD_I2C(
+ self.i2c, self.columns,
+ self.rows, self.address
+ )
+
+ self.lcd.backlight = True
+ self.lcd.clear()
+ self.lcd.message = "MudPi\nGarden Online"
+ time.sleep(2)
+ self.lcd.clear()
+ return
+
+ def run(self):
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'LCD Display Worker ...\t\t\t\033[1;32m Online\033[0;0m'.format(
+ **self.config)
+ )
+ return super().run()
+
+ def handle_message(self, message):
+ data = message['data']
+ if data is not None:
+ decoded_message = self.decode_message_data(data)
+
+ try:
+ if decoded_message['event'] == 'Message':
+ if decoded_message.get('data', None):
+ self.add_message_to_queue(
+ decoded_message['data'].get('message', ''),
+ int(
+ decoded_message['data'].get(
+ 'duration',
+ self.default_duration
+ )
+ )
+ )
+ Logger.log(
+ LOG_LEVEL["debug"],
+ 'LCD Message Queued: \033[1;36m{0}\033[0;0m'.format(
+ decoded_message['data'].get('message',
+ ''))
+ )
+
+ elif decoded_message['event'] == 'Clear':
+ self.lcd.clear()
+ Logger.log(LOG_LEVEL["debug"], 'Cleared the LCD Screen')
+ elif decoded_message['event'] == 'ClearQueue':
+ self.message_queue = []
+ Logger.log(LOG_LEVEL["debug"],
+ 'Cleared the LCD Message Queue')
+ except Exception:
+ Logger.log(LOG_LEVEL["error"],
+ 'Error Decoding Message for LCD')
+
+ def add_message_to_queue(self, message, duration=3):
+ # Add message to queue if LCD available
+ if self.lcd_available.is_set():
+
+ # Replace any codes such as [temperature] with a value
+ # found in redis
+ short_codes = re.findall(r'\[(.*?) *\]', message)
+
+ for code in short_codes:
+ data = self.r.get(code)
+
+ if data is None:
+ data = ''
+
+ else:
+ try:
+ data = data.decode('utf-8')
+ except Exception:
+ data = ''
+ message = message.replace('[' + code + ']', str(data))
+
+ new_message = {
+ "message": message.replace("\\n", "\n"),
+ "duration": duration
+ }
+
+ if len(self.message_queue) == self.message_limit:
+ self.message_queue.pop(0)
+
+ self.message_queue.append(new_message)
+
+ msg = {'event': 'MessageQueued', 'data': new_message}
+ self.r.publish(self.topic, json.dumps(msg))
+ return
+
+ def next_message_from_queue(self):
+ if len(self.message_queue) > 0:
+ self.need_new_message = False
+ self.reset_elapsed_time()
+ return self.message_queue.pop(0)
+ else:
+ time.sleep(3) # pause to reduce system load on message loop
+ return None
+
+ def work(self):
+ self.reset_elapsed_time()
+ self.lcd.clear()
+ self.need_new_message = True
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ try:
+ self.pubsub.get_message()
+ if self.lcd_available.is_set():
+ if self.cached_message and not self.need_new_message:
+ if self.current_message != self.cached_message['message']:
+ self.lcd.clear()
+ time.sleep(0.01)
+ self.lcd.message = self.cached_message[
+ 'message']
+ self.current_message = self.cached_message[
+ 'message'] # store message to only display once and prevent flickers
+ if self.elapsed_time() > self.cached_message['duration'] + 1:
+ self.need_new_message = True
+
+ else:
+ if self.need_new_message:
+ # Get first time message after clear
+ self.cached_message = self.next_message_from_queue()
+ else:
+ time.sleep(1)
+
+ except Exception as e:
+ Logger.log(
+ LOG_LEVEL["error"],
+ "LCD Worker \t\033[1;31m Unexpected Error\033[0;0m".format(
+ **self.config)
+ )
+ Logger.log(LOG_LEVEL["error"], "Exception: {0}".format(e))
+ else:
+ # System not ready
+ time.sleep(1)
+ self.reset_elapsed_time()
+
+ time.sleep(0.1)
+
+ # This is only ran after the main thread is shut down
+ # Close the pubsub connection
+ self.pubsub.close()
+ Logger.log(
+ LOG_LEVEL["info"],
+ "LCD Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m".format(
+ **self.config)
+ )
diff --git a/workers/linux/relay_worker.py b/workers/linux/relay_worker.py
new file mode 100644
index 0000000..b0c7444
--- /dev/null
+++ b/workers/linux/relay_worker.py
@@ -0,0 +1,197 @@
+import json
+import sys
+import time
+
+import board
+import digitalio
+
+from .worker import Worker
+
+
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+class RelayWorker(Worker):
+ def __init__(self, config, main_thread_running,
+ system_ready, relay_available, relay_active):
+ super().__init__(config, main_thread_running, system_ready)
+
+ if self.config.get('key', None) is None:
+ raise Exception('No "key" Found in Relay Config')
+ else:
+ self.key = self.config.get('key', '').replace(" ", "_").lower()
+
+ if self.config.get('name', None) is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = self.config['name']
+
+ self.pin_obj = getattr(board, self.config['pin'])
+
+ # Events
+ self.relay_available = relay_available
+ self.relay_active = relay_active
+
+ # Dynamic Properties based on config
+ self.active = False
+ self.topic = self.config.get('topic', '').replace(" ",
+ "/").lower() if self.config.get(
+ 'topic', None) is not None else 'mudpi/relays/' + self.key
+ self.pin_state_off = True if self.config[
+ 'normally_open'] is not None and \
+ self.config['normally_open'] else False
+ self.pin_state_on = False if self.config[
+ 'normally_open'] is not None and \
+ self.config['normally_open'] else True
+
+ # Pubsub Listeners
+ self.pubsub = self.r.pubsub()
+ self.pubsub.subscribe(**{self.topic: self.handle_message})
+
+ self.init()
+ return
+
+ def init(self):
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Relay Worker {0}...\t\t\t\033[1;32m Initializing\033[0;0m'.format(
+ self.key)
+ )
+ self.gpio_pin = digitalio.DigitalInOut(self.pin_obj)
+ self.gpio_pin.switch_to_output()
+ # Close the relay by default, we use the pin state
+ # we determined based on the config at init
+ self.gpio_pin.value = self.pin_state_off
+ time.sleep(0.1)
+
+ # Feature to restore relay state in case of crash
+ # or unexpected shutdown. This will check for last state
+ # stored in redis and set relay accordingly
+ if (self.config.get('restore_last_known_state',
+ None) is not None and self.config.get(
+ 'restore_last_known_state', False) is True):
+ if self.r.get(self.key + '_state'):
+ self.gpio_pin.value = self.pin_state_on
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Restoring Relay \033[1;36m{0} On\033[0;0m'.format(
+ self.key)
+ )
+
+ # Logger.log(
+ # LOG_LEVEL["info"],
+ # 'Relay Worker {0}...\t\t\t\033[1;32m Ready\033[0;0m'.format(self.key)
+ # )
+ return
+
+ def run(self):
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Relay Worker {0}...\t\t\t\033[1;32m Online\033[0;0m'.format(
+ self.key)
+ )
+ return super().run()
+
+ def handle_message(self, message):
+ data = message['data']
+ if data is not None:
+ decoded_message = self.decode_message_data(data)
+
+ try:
+ if decoded_message['event'] == 'Switch':
+ if decoded_message.get('data', None):
+ self.relay_active.set()
+
+ elif decoded_message.get('data', None) == 0:
+ self.relay_active.clear()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Switch Relay \033[1;36m{0}\033[0;0m state to \033[1;36m{1}\033[0;0m'.format(
+ self.key, decoded_message['data'])
+ )
+
+ elif decoded_message['event'] == 'Toggle':
+ state = 'Off' if self.active else 'On'
+
+ if self.relay_active.is_set():
+ self.relay_active.clear()
+
+ else:
+ self.relay_active.set()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Toggle Relay \033[1;36m{0} {1} \033[0;0m'.format(
+ self.key, state)
+ )
+ except Exception:
+ Logger.log(
+ LOG_LEVEL["error"],
+ 'Error Decoding Message for Relay {0}'.format(
+ self.key)
+ )
+
+ def turn_on(self):
+ # Turn on relay if its available
+ if self.relay_available.is_set():
+ if not self.active:
+ self.gpio_pin.value = self.pin_state_on
+ message = {'event': 'StateChanged', 'data': 1}
+ self.r.set(self.key + '_state', 1)
+ self.r.publish(self.topic, json.dumps(message))
+ self.active = True
+ # This is handled by the redis listener now
+ # self.relay_active.set()
+ self.reset_elapsed_time()
+
+ def turn_off(self):
+ # Turn off volkeye to flip off relay
+ if self.relay_available.is_set():
+ if self.active:
+ self.gpio_pin.value = self.pin_state_off
+ message = {'event': 'StateChanged', 'data': 0}
+ self.r.delete(self.key + '_state')
+ self.r.publish(self.topic, json.dumps(message))
+ # This is handled by the redis listener now
+ # self.relay_active.clear()
+ self.active = False
+ self.reset_elapsed_time()
+
+ def work(self):
+ self.reset_elapsed_time()
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+
+ try:
+ self.pubsub.get_message()
+ if self.relay_available.is_set():
+ if self.relay_active.is_set():
+ self.turn_on()
+ else:
+ self.turn_off()
+ else:
+ self.turn_off()
+ time.sleep(1)
+ except Exception:
+ Logger.log(
+ LOG_LEVEL["error"],
+ "Relay Worker \033[1;36m{0}\033[0;0m \t\033[1;31m Unexpected Error\033[0;0m".format(
+ self.key)
+ )
+
+ else:
+ # System not ready relay should be off
+ self.turn_off()
+ time.sleep(1)
+ self.reset_elapsed_time()
+
+ time.sleep(0.1)
+
+ # This is only ran after the main thread is shut down
+ # Close the pubsub connection
+ self.pubsub.close()
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Relay Worker {0} Shutting Down...\t\033[1;32m Complete\033[0;0m".format(
+ self.key)
+ )
diff --git a/workers/linux/sensor_worker.py b/workers/linux/sensor_worker.py
new file mode 100644
index 0000000..65d278e
--- /dev/null
+++ b/workers/linux/sensor_worker.py
@@ -0,0 +1,98 @@
+import json
+import sys
+import time
+
+from .worker import Worker
+
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+class PiSensorWorker(Worker):
+ def __init__(self, config, main_thread_running, system_ready):
+ super().__init__(config, main_thread_running, system_ready)
+ self.topic = config.get('topic', 'sensors').replace(" ", "_").lower()
+ self.sleep_duration = config.get('sleep_duration', 30)
+
+ self.sensors = []
+ self.init()
+ return
+
+ def init(self):
+ for sensor in self.config['sensors']:
+ if sensor.get('type', None) is not None:
+ # Get the sensor from the sensors folder
+ # {sensor name}_sensor.{SensorName}Sensor
+ sensor_type = 'sensors.linux.'
+ sensor_type += sensor.get('type').lower()
+ sensor_type += '_sensor.'
+ sensor_type += sensor.get('type').capitalize() + 'Sensor'
+
+ imported_sensor = self.dynamic_import(sensor_type)
+
+ # Define default kwargs for all sensor types,
+ # conditionally include optional variables below if they exist
+ sensor_kwargs = {
+ 'name': sensor.get('name', None),
+ 'pin': sensor.get('pin', None),
+ 'key': sensor.get('key', None)
+ }
+
+ # optional sensor variables
+ # Model is specific to DHT modules to specify
+ # DHT11 DHT22 or DHT2302
+ if sensor.get('model'):
+ sensor_kwargs['model'] = str(sensor.get('model'))
+
+ new_sensor = imported_sensor(**sensor_kwargs)
+ new_sensor.init_sensor()
+
+ # Set the sensor type and determine if the readings
+ # are critical to operations
+ new_sensor.type = sensor.get('type').lower()
+ if sensor.get('critical', None) is not None:
+ new_sensor.critical = True
+ else:
+ new_sensor.critical = False
+
+ self.sensors.append(new_sensor)
+ # print('{type} Sensor (Pi)
+ # {pin}...\t\t\033[1;32m Ready\033[0;0m'.format(**sensor))
+ return
+
+ def run(self):
+ Logger.log(
+ LOG_LEVEL["info"], 'Pi Sensor Worker [' + str(
+ len(self.sensors)
+ ) + ' Sensors]...\t\t\033[1;32m Online\033[0;0m'
+ )
+ return super().run()
+
+ def work(self):
+ while self.main_thread_running.is_set():
+
+ if self.system_ready.is_set():
+ message = {'event': 'PiSensorUpdate'}
+ readings = {}
+
+ for sensor in self.sensors:
+ result = sensor.read()
+
+ if result is not None:
+ readings[sensor.key] = result
+ self.r.set(sensor.key, json.dumps(result))
+ # print(sensor.name, result)
+
+ if bool(readings):
+ print(readings)
+ message['data'] = readings
+ self.r.publish(self.topic, json.dumps(message))
+ time.sleep(self.sleep_duration)
+
+ time.sleep(2)
+
+ # This is only ran after the main thread is shut down
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Pi Sensor Worker Shutting Down...\t\033[1;32m Complete\033[0;0m"
+ )
diff --git a/workers/linux/worker.py b/workers/linux/worker.py
new file mode 100644
index 0000000..d680922
--- /dev/null
+++ b/workers/linux/worker.py
@@ -0,0 +1,91 @@
+import json
+import sys
+import threading
+import time
+
+import redis
+
+
+
+from logger.Logger import Logger, LOG_LEVEL
+
+
+class Worker:
+ """
+ Base Worker Class responsible for handling its set of operations
+ and running on a thread
+
+ """
+ def __init__(self, config, main_thread_running, system_ready):
+ self.config = config
+ try:
+ self.r = config["redis"]
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
+ self.topic = config.get('topic', 'mudpi').replace(" ", "_").lower()
+ self.sleep_duration = config.get('sleep_duration', 15)
+
+ # Threading Events to Keep Everything in Sync
+ self.main_thread_running = main_thread_running
+ self.system_ready = system_ready
+ self.worker_available = threading.Event()
+
+ self.components = []
+ return
+
+ def init(self):
+ # print('Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(**control))
+ pass
+
+ def run(self):
+ thread = threading.Thread(target=self.work, args=())
+ thread.start()
+ return thread
+
+ def work(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ time.sleep(self.sleep_duration)
+ # This is only ran after the main thread is shut down
+ Logger.log(
+ LOG_LEVEL["info"],
+ "Worker Shutting Down...\t\033[1;32m Complete\033[0;0m"
+ )
+
+ def elapsed_time(self):
+ self.time_elapsed = time.perf_counter() - self.time_start
+ return self.time_elapsed
+
+ def reset_elapsed_time(self):
+ self.time_start = time.perf_counter()
+ pass
+
+ def dynamic_import(self, name):
+ # Split path of the class folder structure:
+ # {sensor name}_sensor . {SensorName}Sensor
+ components = name.split('.')
+ # Dynamically import root of component path
+ module = __import__(components[0])
+ # Get component attributes
+ for component in components[1:]:
+ module = getattr(module, component)
+ return module
+
+ def decode_message_data(self, message):
+
+ if isinstance(message, dict):
+ # print('Dict Found')
+ return message
+
+ elif isinstance(message.decode('utf-8'), str):
+ try:
+ temp = json.loads(message.decode('utf-8'))
+ # print('Json Found')
+ return temp
+
+ except Exception:
+ # print('Json Error. Str Found')
+ return {'event': 'Unknown', 'data': message}
+ else:
+ # print('Failed to detect type')
+ return {'event': 'Unknown', 'data': message}
diff --git a/workers/pi/camera_worker.py b/workers/pi/camera_worker.py
deleted file mode 100644
index 0b6716e..0000000
--- a/workers/pi/camera_worker.py
+++ /dev/null
@@ -1,147 +0,0 @@
-import time
-import datetime
-import json
-import redis
-import threading
-import sys
-import os
-import RPi.GPIO as GPIO
-from picamera import PiCamera
-from .worker import Worker
-sys.path.append('..')
-
-from logger.Logger import Logger, LOG_LEVEL
-
-
-class CameraWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, camera_available):
- super().__init__(config, main_thread_running, system_ready)
- self.pending_reset = False
-
- # Events
- self.camera_available = camera_available
-
- # Dynamic Properties based on config
- self.path = self.config['path'].replace(" ", "-") if self.config['path'] is not None else '/etc/mudpi/img/'
- self.topic = self.config['topic'].replace(" ", "/").lower() if self.config['topic'] is not None else 'mudpi/camera/'
- if self.config['resolution'] is not None:
- self.resolutionX = int(self.config['resolution'].get('x', 1920))
- self.resolutionY = int(self.config['resolution'].get('y', 1080))
- if self.config['delay'] is not None:
- self.hours = int(self.config['delay'].get('hours', 0))
- self.minutes = int(self.config['delay'].get('minutes', 0))
- self.seconds = int(self.config['delay'].get('seconds', 0))
-
- self.init()
- return
-
- def init(self):
- try:
- self.camera = PiCamera(resolution=(self.resolutionX, self.resolutionY))
- # Below we calibrate the camera for consistent imaging
- self.camera.framerate = 30
- # Wait for the automatic gain control to settle
- time.sleep(2)
- # Now fix the values
- self.camera.shutter_speed = self.camera.exposure_speed
- self.camera.exposure_mode = 'off'
- g = self.camera.awb_gains
- self.camera.awb_mode = 'off'
- self.camera.awb_gains = g
- except:
- self.camera = PiCamera()
-
- #Pubsub Listeners
- self.pubsub = self.r.pubsub()
- self.pubsub.subscribe(**{self.topic: self.handleEvent})
-
- Logger.log(LOG_LEVEL["info"], 'Camera Worker...\t\t\t\033[1;32m Ready\033[0;0m')
- return
-
- def run(self):
- t = threading.Thread(target=self.work, args=())
- t.start()
- self.listener = threading.Thread(target=self.listen, args=())
- self.listener.start()
- Logger.log(LOG_LEVEL["info"], 'Camera Worker...\t\t\t\033[1;32m Online\033[0;0m')
- return t
-
- def wait(self):
- # Calculate the delay
- try:
- self.next_time = (datetime.datetime.now() + datetime.timedelta(hours=self.hours, minutes=self.minutes, seconds=self.seconds)).replace(microsecond=0)
- except:
- # Default every hour
- self.next_time = (datetime.datetime.now() + datetime.timedelta(hours=1)).replace(minute=0, second=0, microsecond=0)
- delay = (self.next_time - datetime.datetime.now()).seconds
- time.sleep(delay)
-
- def handleEvent(self, message):
- data = message['data']
- decoded_message = None
- if data is not None:
- try:
- if isinstance(data, dict):
- decoded_message = data
- elif isinstance(data.decode('utf-8'), str):
- temp = json.loads(data.decode('utf-8'))
- decoded_message = temp
- if decoded_message['event'] == 'Timelapse':
- Logger.log(LOG_LEVEL["info"], "Camera Signaled for Reset")
- self.camera_available.clear()
- self.pending_reset = True
- except:
- Logger.log(LOG_LEVEL["error"], 'Error Handling Event for Camera')
-
- def listen(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- if self.camera_available.is_set():
- self.pubsub.get_message()
- time.sleep(1)
- else:
- delay = (self.next_time - datetime.datetime.now()).seconds + 15
- time.sleep(delay) #wait 15 seconds after next scheduled picture
- self.camera_available.set()
- else:
- time.sleep(2)
- return
-
- def work(self):
- self.resetElapsedTime()
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- if self.camera_available.is_set():
- # try:
- for i, filename in enumerate(self.camera.capture_continuous(self.path + 'mudpi-{counter:05d}.jpg')):
- if not self.camera_available.is_set():
- if self.pending_reset:
- try:
- os.remove(filename) #cleanup previous file
- self.pending_reset = False
- except:
- Logger.log(LOG_LEVEL["error"], "Error During Camera Reset Cleanup")
- break;
- message = {'event':'StateChanged', 'data':filename}
- self.r.set('last_camera_image', filename)
- self.r.publish(self.topic, json.dumps(message))
- Logger.log(LOG_LEVEL["debug"], 'Image Captured \033[1;36m%s\033[0;0m' % filename)
- self.wait()
- # except:
- # print("Camera Worker \t\033[1;31m Unexpected Error\033[0;0m")
- # time.sleep(30)
- else:
- time.sleep(1)
- self.resetElapsedTime()
- else:
- #System not ready camera should be off
- time.sleep(1)
- self.resetElapsedTime()
-
- time.sleep(0.1)
-
- #This is only ran after the main thread is shut down
- self.camera.close()
- self.listener.join()
- self.pubsub.close()
- Logger.log(LOG_LEVEL["info"], "Camera Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m")
\ No newline at end of file
diff --git a/workers/pi/control_worker.py b/workers/pi/control_worker.py
deleted file mode 100644
index 9ff0050..0000000
--- a/workers/pi/control_worker.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import time
-import datetime
-import json
-import redis
-import threading
-from .worker import Worker
-import sys
-sys.path.append('..')
-from controls.pi.button_control import (ButtonControl)
-from controls.pi.switch_control import (SwitchControl)
-
-from logger.Logger import Logger, LOG_LEVEL
-
-class PiControlWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready):
- super().__init__(config, main_thread_running, system_ready)
- self.topic = config.get('topic', 'controls').replace(" ", "_").lower()
- self.sleep_duration = config.get('sleep_duration', 0.5)
-
- self.controls = []
- self.init()
- return
-
- def init(self):
- for control in self.config['controls']:
- if control.get('type', None) is not None:
- #Get the control from the controls folder {control name}_control.{ControlName}Control
- control_type = 'controls.pi.' + control.get('type').lower() + '_control.' + control.get('type').capitalize() + 'Control'
-
- imported_control = self.dynamic_import(control_type)
- #new_control = imported_control(control.get('pin'), name=control.get('name', control.get('type')), connection=self.connection, key=control.get('key', None))
-
- # Define default kwargs for all control types, conditionally include optional variables below if they exist
- control_kwargs = {
- 'name' : control.get('name', None),
- 'pin' : int(control.get('pin')),
- 'key' : control.get('key', None),
- 'topic': control.get('topic', None),
- 'resistor': control.get('resistor', None),
- 'edge_detection': control.get('edge_detection', None),
- 'debounce': control.get('debounce', None)
- }
-
- # optional control variables
- # add conditional control vars here...
-
- new_control = imported_control(**control_kwargs)
-
- new_control.init_control()
- self.controls.append(new_control)
- Logger.log(LOG_LEVEL["info"], '{type} Control {pin}...\t\t\t\033[1;32m Ready\033[0;0m'.format(**control))
- return
-
- def run(self):
- Logger.log(LOG_LEVEL["info"], 'Pi Control Worker [' + str(len(self.config['controls'])) + ' Controls]...\t\033[1;32m Online\033[0;0m')
- return super().run()
-
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- readings = {}
- for control in self.controls:
- result = control.read()
- readings[control.key] = result
- time.sleep(self.sleep_duration)
- #This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "Pi Control Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
\ No newline at end of file
diff --git a/workers/pi/i2c_worker.py b/workers/pi/i2c_worker.py
deleted file mode 100644
index 6aad813..0000000
--- a/workers/pi/i2c_worker.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import time
-import datetime
-import json
-import redis
-import threading
-import sys
-sys.path.append('..')
-from .worker import Worker
-from sensors.pi.i2c.bme680_sensor import (Bme680Sensor)
-
-from logger.Logger import Logger, LOG_LEVEL
-
-
-class PiI2CWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready):
- super().__init__(config, main_thread_running, system_ready)
- self.topic = config.get('topic', 'i2c').replace(" ", "_").lower()
- self.sleep_duration = config.get('sleep_duration', 30)
-
- self.sensors = []
- self.init()
- return
-
- def init(self):
- for sensor in self.config['sensors']:
- if sensor.get('type', None) is not None:
- #Get the sensor from the sensors folder {sensor name}_sensor.{SensorName}Sensor
- sensor_type = 'sensors.pi.i2c.' + sensor.get('type').lower() + '_sensor.' + sensor.get('type').capitalize() + 'Sensor'
-
- imported_sensor = self.dynamic_import(sensor_type)
-
- # Define default kwargs for all sensor types, conditionally include optional variables below if they exist
- sensor_kwargs = {
- 'name' : sensor.get('name', None),
- 'address' : int(sensor.get('address', 00)),
- 'key' : sensor.get('key', None)
- }
-
- # Optional sensor variables
- # Model is specific to DHT modules to specify DHT11 DHT22 or DHT2302
- if sensor.get('model'):
- sensor_kwargs['model'] = str(sensor.get('model'))
-
- new_sensor = imported_sensor(**sensor_kwargs)
- new_sensor.init_sensor()
-
- #Set the sensor type and determine if the readings are critical to operations
- new_sensor.type = sensor.get('type').lower()
-
- self.sensors.append(new_sensor)
- # print('{type} Sensor (Pi) {address}...\t\t\033[1;32m Ready\033[0;0m'.format(**sensor))
- return
-
- def run(self):
- Logger.log(LOG_LEVEL["info"], 'Pi I2C Sensor Worker [' + str(len(self.sensors)) + ' Sensors]...\t\033[1;32m Online\033[0;0m')
- return super().run()
-
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
-
- message = {'event':'PiSensorUpdate'}
- readings = {}
-
- for sensor in self.sensors:
- result = sensor.read()
- readings[sensor.key] = result
- self.r.set(sensor.key, json.dumps(result))
-
- message['data'] = readings
- Logger.log(LOG_LEVEL["debug"], readings);
- self.r.publish(self.topic, json.dumps(message))
- time.sleep(self.sleep_duration)
-
- time.sleep(2)
- #This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "Pi I2C Sensor Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
\ No newline at end of file
diff --git a/workers/pi/lcd_worker.py b/workers/pi/lcd_worker.py
deleted file mode 100644
index 1200af0..0000000
--- a/workers/pi/lcd_worker.py
+++ /dev/null
@@ -1,182 +0,0 @@
-import re
-import time
-import json
-import redis
-import board
-import busio
-import datetime
-import threading
-import adafruit_character_lcd.character_lcd_rgb_i2c as character_rgb_lcd
-import adafruit_character_lcd.character_lcd_i2c as character_lcd
-from .worker import Worker
-
-from logger.Logger import Logger, LOG_LEVEL
-
-class LcdWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, lcd_available):
- super().__init__(config, main_thread_running, system_ready)
- try:
- self.address = int(self.config['address']) if self.config['address'] is not None else None
- except KeyError:
- self.address = None
- try:
- self.model = str(self.config['model']) if self.config['model'] is not None else ''
- except KeyError:
- self.model = ''
- try:
- self.columns = int(self.config['columns']) if self.config['columns'] is not None else 16
- except KeyError:
- self.columns = 16
- try:
- self.rows = int(self.config['rows']) if self.config['rows'] is not None else 2
- except KeyError:
- self.rows = 2
- try:
- self.default_duration = int(self.config['default_duration']) if self.config['default_duration'] is not None else 5
- except KeyError:
- self.default_duration = 5
-
- self.current_message = ""
- self.cached_message = {'message':'', 'duration': self.default_duration}
- self.need_new_message = True
- self.message_queue = []
- self.message_limit = int(self.config['message_limit']) if self.config.get('message_limit', None) is not None else 20
-
- # Events
- self.lcd_available = lcd_available
-
- # Dynamic Properties based on config
- try:
- self.topic = self.config['topic'].replace(" ", "/").lower() if self.config['topic'] is not None else 'mudpi/lcd'
- except KeyError:
- self.topic = 'mudpi/lcd'
-
- # Pubsub Listeners
- self.pubsub = self.r.pubsub()
- self.pubsub.subscribe(**{self.topic: self.handleMessage})
-
- self.init()
- return
-
- def init(self):
- Logger.log(LOG_LEVEL["info"], 'LCD Display Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(**self.config))
- # prepare sensor on specified pin
- self.i2c = busio.I2C(board.SCL, board.SDA)
- if(self.model):
- if (self.model.lower() == 'rgb'):
- self.lcd = character_lcd.Character_LCD_RGB_I2C(self.i2c, self.columns, self.rows, self.address)
- elif (self.model.lower() == 'pcf'):
- self.lcd = character_lcd.Character_LCD_I2C(self.i2c, self.columns, self.rows, address=self.address, usingPCF=True)
- else:
- self.lcd = character_lcd.Character_LCD_I2C(self.i2c, self.columns, self.rows, self.address)
- else:
- self.lcd = character_lcd.Character_LCD_I2C(self.i2c, self.columns, self.rows, self.address)
-
- self.lcd.backlight = True
- self.lcd.clear()
- self.lcd.message = "MudPi\nGarden Online"
- time.sleep(2)
- self.lcd.clear()
- return
-
- def run(self):
- Logger.log(LOG_LEVEL["info"], 'LCD Display Worker ...\t\t\t\033[1;32m Online\033[0;0m'.format(**self.config))
- return super().run()
-
- def handleMessage(self, message):
- data = message['data']
- if data is not None:
- decoded_message = self.decodeMessageData(data)
- try:
- if decoded_message['event'] == 'Message':
- if decoded_message.get('data', None):
- self.addMessageToQueue(decoded_message['data'].get('message', ''), int(decoded_message['data'].get('duration', self.default_duration)))
- Logger.log(LOG_LEVEL["debug"], 'LCD Message Queued: \033[1;36m{0}\033[0;0m'.format(decoded_message['data'].get('message', '')))
-
- elif decoded_message['event'] == 'Clear':
- self.lcd.clear()
- Logger.log(LOG_LEVEL["debug"], 'Cleared the LCD Screen')
- elif decoded_message['event'] == 'ClearQueue':
- self.message_queue = []
- Logger.log(LOG_LEVEL["debug"], 'Cleared the LCD Message Queue')
- except:
- Logger.log(LOG_LEVEL["error"], 'Error Decoding Message for LCD')
-
- def addMessageToQueue(self, message, duration = 3):
- #Add message to queue if LCD available
- if self.lcd_available.is_set():
-
- # Replace any codes such as [temperature] with a value found in redis
- short_codes = re.findall(r'\[(.*?) *\]', message)
-
- for code in short_codes:
- data = self.r.get(code)
- if data is None:
- data = ''
- else:
- try:
- data = data.decode('utf-8')
- except:
- data = ''
- message = message.replace('['+code+']', str(data))
-
- new_message = {
- "message": message.replace("\\n", "\n"),
- "duration": duration
- }
-
- if len(self.message_queue) == self.message_limit:
- self.message_queue.pop(0)
-
- self.message_queue.append(new_message)
-
- msg = { 'event':'MessageQueued', 'data': new_message }
- self.r.publish(self.topic, json.dumps(msg))
- return
-
- def nextMessageFromQueue(self):
- if len(self.message_queue) > 0:
- self.need_new_message = False
- self.resetElapsedTime()
- return self.message_queue.pop(0)
- else:
- time.sleep(3) # pause to reduce system load on message loop
- return None
-
- def work(self):
- self.resetElapsedTime()
- self.lcd.clear()
- self.need_new_message = True
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- try:
- self.pubsub.get_message()
- if self.lcd_available.is_set():
- if self.cached_message and not self.need_new_message:
- if self.current_message != self.cached_message['message']:
- self.lcd.clear()
- time.sleep(0.01)
- self.lcd.message = self.cached_message['message']
- self.current_message = self.cached_message['message'] # store message to only display once and prevent flickers
- if self.elapsedTime() > self.cached_message['duration'] + 1:
- self.need_new_message = True
- else:
- if self.need_new_message:
- # Get first time message after clear
- self.cached_message = self.nextMessageFromQueue()
- else:
- time.sleep(1)
- except Exception as e:
- Logger.log(LOG_LEVEL["error"], "LCD Worker \t\033[1;31m Unexpected Error\033[0;0m".format(**self.config))
- Logger.log(LOG_LEVEL["error"], "Exception: {0}".format(e))
- else:
- # System not ready
- time.sleep(1)
- self.resetElapsedTime()
-
- time.sleep(0.1)
-
- #This is only ran after the main thread is shut down
- #Close the pubsub connection
- self.pubsub.close()
- Logger.log(LOG_LEVEL["info"], "LCD Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m".format(**self.config))
\ No newline at end of file
diff --git a/workers/pi/relay_worker.py b/workers/pi/relay_worker.py
deleted file mode 100644
index 6737679..0000000
--- a/workers/pi/relay_worker.py
+++ /dev/null
@@ -1,141 +0,0 @@
-import time
-import datetime
-import json
-import redis
-import threading
-import sys
-import RPi.GPIO as GPIO
-from .worker import Worker
-sys.path.append('..')
-
-from logger.Logger import Logger, LOG_LEVEL
-
-class RelayWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, relay_available, relay_active):
- super().__init__(config, main_thread_running, system_ready)
-
- if self.config.get('key', None) is None:
- raise Exception('No "key" Found in Relay Config')
- else:
- self.key = self.config.get('key', '').replace(" ", "_").lower()
-
- if self.config.get('name', None) is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = self.config['name']
-
- self.config['pin'] = int(self.config['pin']) # parse possbile strings to avoid errors
-
- # Events
- self.relay_available = relay_available
- self.relay_active = relay_active
-
- # Dynamic Properties based on config
- self.active = False
- self.topic = self.config.get('topic', '').replace(" ", "/").lower() if self.config.get('topic', None) is not None else 'mudpi/relays/'+self.key
- self.pin_state_off = GPIO.HIGH if self.config['normally_open'] is not None and self.config['normally_open'] else GPIO.LOW
- self.pin_state_on = GPIO.LOW if self.config['normally_open'] is not None and self.config['normally_open'] else GPIO.HIGH
-
- # Pubsub Listeners
- self.pubsub = self.r.pubsub()
- self.pubsub.subscribe(**{self.topic: self.handleMessage})
-
- self.init()
- return
-
- def init(self):
- Logger.log(LOG_LEVEL["info"], 'Relay Worker {0}...\t\t\t\033[1;32m Initializing\033[0;0m'.format(self.key))
- GPIO.setup(self.config['pin'], GPIO.OUT)
- #Close the relay by default, we use the pin state we determined based on the config at init
- GPIO.output(self.config['pin'], self.pin_state_off)
- time.sleep(0.1)
-
- #Feature to restore relay state in case of crash or unexpected shutdown. This will check for last state stored in redis and set relay accordingly
- if(self.config.get('restore_last_known_state', None) is not None and self.config.get('restore_last_known_state', False) is True):
- if(self.r.get(self.key+'_state')):
- GPIO.output(self.config['pin'], self.pin_state_on)
- Logger.log(LOG_LEVEL["info"], 'Restoring Relay \033[1;36m{0} On\033[0;0m'.format(self.key))
-
-
- # Logger.log(LOG_LEVEL["info"], 'Relay Worker {0}...\t\t\t\033[1;32m Ready\033[0;0m'.format(self.key))
- return
-
- def run(self):
- Logger.log(LOG_LEVEL["info"], 'Relay Worker {0}...\t\t\t\033[1;32m Online\033[0;0m'.format(self.key))
- return super().run()
-
- def handleMessage(self, message):
- data = message['data']
- if data is not None:
- decoded_message = self.decodeMessageData(data)
- try:
- if decoded_message['event'] == 'Switch':
- if decoded_message.get('data', None):
- self.relay_active.set()
- elif decoded_message.get('data', None) == 0:
- self.relay_active.clear()
- Logger.log(LOG_LEVEL["info"], 'Switch Relay \033[1;36m{0}\033[0;0m state to \033[1;36m{1}\033[0;0m'.format(self.key, decoded_message['data']))
- elif decoded_message['event'] == 'Toggle':
- state = 'Off' if self.active else 'On'
- if self.relay_active.is_set():
- self.relay_active.clear()
- else:
- self.relay_active.set()
- Logger.log(LOG_LEVEL["info"], 'Toggle Relay \033[1;36m{0} {1} \033[0;0m'.format(self.key, state))
- except:
- Logger.log(LOG_LEVEL["error"], 'Error Decoding Message for Relay {0}'.format(self.key))
-
- def turnOn(self):
- #Turn on relay if its available
- if self.relay_available.is_set():
- if not self.active:
- GPIO.output(self.config['pin'], self.pin_state_on)
- message = {'event':'StateChanged', 'data':1}
- self.r.set(self.key+'_state', 1)
- self.r.publish(self.topic, json.dumps(message))
- self.active = True
- #self.relay_active.set() This is handled by the redis listener now
- self.resetElapsedTime()
-
- def turnOff(self):
- #Turn off volkeye to flip off relay
- if self.relay_available.is_set():
- if self.active:
- GPIO.output(self.config['pin'], self.pin_state_off)
- message = {'event':'StateChanged', 'data':0}
- self.r.delete(self.key+'_state')
- self.r.publish(self.topic, json.dumps(message))
- #self.relay_active.clear() This is handled by the redis listener now
- self.active = False
- self.resetElapsedTime()
-
- def work(self):
- self.resetElapsedTime()
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
-
- try:
- self.pubsub.get_message()
- if self.relay_available.is_set():
- if self.relay_active.is_set():
- self.turnOn()
- else:
- self.turnOff()
- else:
- self.turnOff()
- time.sleep(1)
- except:
- Logger.log(LOG_LEVEL["error"], "Relay Worker \033[1;36m{0}\033[0;0m \t\033[1;31m Unexpected Error\033[0;0m".format(self.key))
-
- else:
- #System not ready relay should be off
- self.turnOff()
- time.sleep(1)
- self.resetElapsedTime()
-
- time.sleep(0.1)
-
- # This is only ran after the main thread is shut down
- # Close the pubsub connection
- self.pubsub.close()
- Logger.log(LOG_LEVEL["info"], "Relay Worker {0} Shutting Down...\t\033[1;32m Complete\033[0;0m".format(self.key))
diff --git a/workers/pi/sensor_worker.py b/workers/pi/sensor_worker.py
deleted file mode 100644
index f2e2722..0000000
--- a/workers/pi/sensor_worker.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import time
-import datetime
-import json
-import redis
-import threading
-from .worker import Worker
-import sys
-sys.path.append('..')
-from sensors.pi.float_sensor import (FloatSensor)
-from sensors.pi.humidity_sensor import (HumiditySensor)
-from logger.Logger import Logger, LOG_LEVEL
-
-class PiSensorWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready):
- super().__init__(config, main_thread_running, system_ready)
- self.topic = config.get('topic', 'sensors').replace(" ", "_").lower()
- self.sleep_duration = config.get('sleep_duration', 30)
-
- self.sensors = []
- self.init()
- return
-
- def init(self):
- for sensor in self.config['sensors']:
- if sensor.get('type', None) is not None:
- #Get the sensor from the sensors folder {sensor name}_sensor.{SensorName}Sensor
- sensor_type = 'sensors.pi.' + sensor.get('type').lower() + '_sensor.' + sensor.get('type').capitalize() + 'Sensor'
-
- imported_sensor = self.dynamic_import(sensor_type)
-
- # Define default kwargs for all sensor types, conditionally include optional variables below if they exist
- sensor_kwargs = {
- 'name' : sensor.get('name', None),
- 'pin' : int(sensor.get('pin', 0)),
- 'key' : sensor.get('key', None)
- }
-
- # optional sensor variables
- # Model is specific to DHT modules to specify DHT11 DHT22 or DHT2302
- if sensor.get('model'):
- sensor_kwargs['model'] = str(sensor.get('model'))
-
- new_sensor = imported_sensor(**sensor_kwargs)
- new_sensor.init_sensor()
-
- #Set the sensor type and determine if the readings are critical to operations
- new_sensor.type = sensor.get('type').lower()
- if sensor.get('critical', None) is not None:
- new_sensor.critical = True
- else:
- new_sensor.critical = False
-
- self.sensors.append(new_sensor)
- # print('{type} Sensor (Pi) {pin}...\t\t\033[1;32m Ready\033[0;0m'.format(**sensor))
- return
-
- def run(self):
- Logger.log(LOG_LEVEL["info"], 'Pi Sensor Worker [' + str(len(self.sensors)) + ' Sensors]...\t\t\033[1;32m Online\033[0;0m')
- return super().run()
-
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- message = {'event':'PiSensorUpdate'}
- readings = {}
- for sensor in self.sensors:
- result = sensor.read()
- if result is not None:
- readings[sensor.key] = result
- self.r.set(sensor.key, json.dumps(result))
- #print(sensor.name, result)
-
- if bool(readings):
- print(readings)
- message['data'] = readings
- self.r.publish(self.topic, json.dumps(message))
- time.sleep(self.sleep_duration)
-
- time.sleep(2)
-
- # This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "Pi Sensor Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
\ No newline at end of file
diff --git a/workers/pi/worker.py b/workers/pi/worker.py
deleted file mode 100644
index a1c4e97..0000000
--- a/workers/pi/worker.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import time
-import datetime
-import json
-import redis
-import threading
-import sys
-sys.path.append('..')
-
-import variables
-from logger.Logger import Logger, LOG_LEVEL
-
-# Base Worker Class
-# A worker is responsible for handling its set of operations and running on a thread
-class Worker():
- def __init__(self, config, main_thread_running, system_ready):
- self.config = config
- try:
- self.r = config["redis"]
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
- self.topic = config.get('topic', 'mudpi').replace(" ", "_").lower()
- self.sleep_duration = config.get('sleep_duration', 15)
-
- # Threading Events to Keep Everything in Sync
- self.main_thread_running = main_thread_running
- self.system_ready = system_ready
- self.worker_available = threading.Event()
-
- self.components = []
- return
-
- def init(self):
- # print('Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(**control))
- return
-
- def run(self):
- t = threading.Thread(target=self.work, args=())
- t.start()
- return t
-
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- time.sleep(self.sleep_duration)
- #This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
-
- def elapsedTime(self):
- self.time_elapsed = time.perf_counter() - self.time_start
- return self.time_elapsed
-
- def resetElapsedTime(self):
- self.time_start = time.perf_counter()
- pass
-
- def dynamic_import(self, name):
- # Split path of the class folder structure: {sensor name}_sensor . {SensorName}Sensor
- components = name.split('.')
- # Dynamically import root of component path
- module = __import__(components[0])
- # Get component attributes
- for component in components[1:]:
- module = getattr(module, component)
- return module
-
- def decodeMessageData(self, message):
- if isinstance(message, dict):
- #print('Dict Found')
- return message
- elif isinstance(message.decode('utf-8'), str):
- try:
- temp = json.loads(message.decode('utf-8'))
- #print('Json Found')
- return temp
- except:
- #print('Json Error. Str Found')
- return {'event':'Unknown', 'data':message}
- else:
- #print('Failed to detect type')
- return {'event':'Unknown', 'data':message}
\ No newline at end of file
diff --git a/workers/sequence_worker.py b/workers/sequence_worker.py
index 01ad576..c938b02 100644
--- a/workers/sequence_worker.py
+++ b/workers/sequence_worker.py
@@ -5,294 +5,358 @@
import threading
from .worker import Worker
import sys
-sys.path.append('..')
-import variables
+
+
+import constants
import importlib
from logger.Logger import Logger, LOG_LEVEL
+
class SequenceWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, sequence_available, sequence_active, actions):
- super().__init__(config, main_thread_running, system_ready)
+ def __init__(self, config, main_thread_running, system_ready,
+ sequence_available, sequence_active, actions):
+ super().__init__(config, main_thread_running, system_ready)
+
+ if self.config.get('key', None) is None:
+ raise Exception('No "key" Found in Sequence Config')
+ else:
+ self.key = self.config['key'].replace(" ", "_").lower()
- if self.config.get('key', None) is None:
- raise Exception('No "key" Found in Sequence Config')
- else:
- self.key = self.config['key'].replace(" ", "_").lower()
+ if self.config.get('name', None) is None:
+ self.name = self.key.replace("_", " ").title()
+ else:
+ self.name = self.config.get('name', '')
- if self.config.get('name', None) is None:
- self.name = self.key.replace("_", " ").title()
- else:
- self.name = self.config.get('name', '')
+ self.actions = actions
+ self.sequence = self.config['sequence'] if self.config[
+ 'sequence'] is not None else []
+ self.key = self.config.get(
+ 'key', '').replace(" ", "_").lower() if self.config.get(
+ 'key', None) is not None else self.name.replace(" ", "_").lower()
+ self.topic = self.config.get(
+ 'topic', '').replace(" ", "/").lower() if self.config.get(
+ 'topic', None) is not None else 'mudpi/sequences/' + self.key
+ self.current_step = 0
+ self.total_steps = len(self.sequence)
+ self.delay_complete = False
+ self.step_complete = False
+ self.step_triggered = False
- self.actions = actions
- self.sequence = self.config['sequence'] if self.config['sequence'] is not None else []
- self.key = self.config.get('key', '').replace(" ", "_").lower() if self.config.get('key', None) is not None else self.name.replace(" ", "_").lower()
- self.topic = self.config.get('topic', '').replace(" ", "/").lower() if self.config.get('topic', None) is not None else 'mudpi/sequences/'+self.key
- self.current_step = 0
- self.total_steps = len(self.sequence)
- self.delay_complete = False
- self.step_complete = False
- self.step_triggered = False
+ # Events
+ self.sequence_available = sequence_available
+ self.sequence_active = sequence_active
- # Events
- self.sequence_available = sequence_available
- self.sequence_active = sequence_active
+ # PubSub
+ try:
+ self.r = config["redis"]
+ except KeyError:
+ self.r = redis.Redis(host='127.0.0.1', port=6379)
- # PubSub
- try:
- self.r = config["redis"]
- except KeyError:
- self.r = redis.Redis(host='127.0.0.1', port=6379)
+ # Pubsub Listeners
+ self.pubsub = self.r.pubsub()
- # Pubsub Listeners
- self.pubsub = self.r.pubsub()
+ self.reset_elapsed_time()
+ self.init()
+ return
- self.resetElapsedTime()
- self.init()
- return
+ def init(self):
+ self.pubsub.subscribe(**{self.topic: self.handle_message})
+ # Logger.log(LOG_LEVEL["info"], '{type} - {name}...\t\t\033[1;32m Listening\033[0;0m'.format(**trigger))
+ return
- def init(self):
- self.pubsub.subscribe(**{self.topic: self.handleMessage})
- # Logger.log(LOG_LEVEL["info"], '{type} - {name}...\t\t\033[1;32m Listening\033[0;0m'.format(**trigger))
- return
+ def reset_step(self):
+ self.delay_complete = False
+ self.step_triggered = False
+ self.step_complete = False
- def reset_step(self):
- self.delay_complete = False
- self.step_triggered = False
- self.step_complete = False
+ def update(self, value=None):
+ if not self.sequence_active.is_set():
+ self.start()
+ else:
+ if self.sequence[self.current_step].get('duration',
+ None) is None and self.delay_complete:
+ self.step_complete = True
+ self.next_step()
- def update(self, value=None):
- if not self.sequence_active.is_set():
- self.start()
- else:
- if self.sequence[self.current_step].get('duration', None) is None and self.delay_complete:
- self.step_complete = True
- self.next_step()
+ def start(self):
+ if not self.sequence_active.is_set():
+ self.current_step = 0
+ self.sequence_active.set()
+ self.reset_step()
+ self.r.publish(self.topic, json.dumps({
+ "event": "SequenceStarted",
+ "data": {
+ "name": self.name,
+ "key": self.key
+ }
+ }))
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequence {0} Started\033[0;0m'.format(self.name)
+ )
- def start(self):
- if not self.sequence_active.is_set():
- self.current_step = 0
- self.sequence_active.set()
- self.reset_step()
- self.r.publish(self.topic, json.dumps({
- "event": "SequenceStarted",
- "data": {
- "name": self.name,
- "key": self.key
- }
- }))
- Logger.log(LOG_LEVEL["info"], 'Sequence {0} Started\033[0;0m'.format(self.name))
+ def stop(self):
+ if self.sequence_active.is_set():
+ self.current_step = 0
+ self.sequence_active.clear()
+ self.reset_step()
+ self.r.publish(self.topic, json.dumps({
+ "event": "SequenceStopped",
+ "data": {
+ "name": self.name,
+ "key": self.key
+ }
+ }))
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequence {0} Stopped\033[0;0m'.format(self.name)
+ )
- def stop(self):
- if self.sequence_active.is_set():
- self.current_step = 0
- self.sequence_active.clear()
- self.reset_step()
- self.r.publish(self.topic, json.dumps({
- "event": "SequenceStopped",
- "data": {
- "name": self.name,
- "key": self.key
- }
- }))
- Logger.log(LOG_LEVEL["info"], 'Sequence {0} Stopped\033[0;0m'.format(self.name))
+ def next_step(self):
+ if self.step_complete:
+ # Step must be flagged complete to advance
+ if self.sequence_active.is_set():
+ # If skipping steps trigger unperformed actions
+ if not self.step_triggered:
+ if self.evaluate_thresholds():
+ self.trigger()
+ # Sequence is already active, advance to next step
+ if self.current_step < self.total_steps - 1:
+ self.reset_step()
+ self.current_step += 1
+ self.r.publish(
+ self.topic,
+ json.dumps(
+ {
+ "event": "SequenceStepStarted",
+ "data": {
+ "name": self.name,
+ "key": self.key,
+ "step": self.current_step
+ }
+ }
+ )
+ )
+ else:
+ # Last step of seqence completed
+ self.sequence_active.clear()
+ self.r.publish(self.topic, json.dumps({
+ "event": "SequenceEnded",
+ "data": {
+ "name": self.name,
+ "key": self.key
+ }
+ }))
+ self.reset_elapsed_time()
- def next_step(self):
- if self.step_complete:
- # Step must be flagged complete to advance
- if self.sequence_active.is_set():
- # If skipping steps trigger unperformed actions
- if not self.step_triggered:
- if self.evaluateThresholds():
- self.trigger()
- # Sequence is already active, advance to next step
- if self.current_step < self.total_steps - 1:
- self.reset_step()
- self.current_step+=1
- self.r.publish(self.topic, json.dumps({
- "event": "SequenceStepStarted",
- "data": {
- "name": self.name,
- "key": self.key,
- "step": self.current_step
- }
- }))
- else:
- # Last step of seqence completed
- self.sequence_active.clear()
- self.r.publish(self.topic, json.dumps({
- "event": "SequenceEnded",
- "data": {
- "name": self.name,
- "key": self.key
- }
- }))
- self.resetElapsedTime()
+ def previous_step(self):
+ if self.current_step > 0:
+ self.reset_step()
+ self.current_step -= 1
+ self.r.publish(self.topic, json.dumps({
+ "event": "SequenceStepStarted",
+ "data": {
+ "name": self.name,
+ "key": self.key,
+ "step": self.current_step
+ }
+ }))
+ self.reset_elapsed_time()
- def previous_step(self):
- if self.current_step > 0:
- self.reset_step()
- self.current_step-=1
- self.r.publish(self.topic, json.dumps({
- "event": "SequenceStepStarted",
- "data": {
- "name": self.name,
- "key": self.key,
- "step": self.current_step
- }
- }))
- self.resetElapsedTime()
+ def handle_message(self, message):
+ data = message['data']
+ if data is not None:
+ decoded_message = self.last_event = self.decode_message_data(data)
+ try:
+ if decoded_message['event'] == 'SequenceNextStep':
+ if self.sequence[self.current_step].get('duration',
+ None) is None and self.delay_complete:
+ self.step_complete = True
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequence {0} Next Step Triggered\033[0;0m'.format(
+ self.name)
+ )
+ elif decoded_message['event'] == 'SequencePreviousStep':
+ self.previous_step()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequence {0} Previous Step Triggered\033[0;0m'.format(
+ self.name)
+ )
+ elif decoded_message['event'] == 'SequenceStart':
+ self.start()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequence {0} Start Triggered\033[0;0m'.format(
+ self.name)
+ )
+ elif decoded_message['event'] == 'SequenceSkipStep':
+ self.step_complete = True
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequence {0} Skip Step Triggered\033[0;0m'.format(
+ self.name)
+ )
+ elif decoded_message['event'] == 'SequenceStop':
+ self.stop()
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Sequence {0} Stop Triggered\033[0;0m'.format(
+ self.name)
+ )
+ except:
+ Logger.log(
+ LOG_LEVEL["info"],
+ 'Error Decoding Message for Sequence {0}'.format(
+ self.config['key'])
+ )
- def handleMessage(self, message):
- data = message['data']
- if data is not None:
- decoded_message = self.last_event = self.decodeMessageData(data)
- try:
- if decoded_message['event'] == 'SequenceNextStep':
- if self.sequence[self.current_step].get('duration', None) is None and self.delay_complete:
- self.step_complete = True
- Logger.log(LOG_LEVEL["info"], 'Sequence {0} Next Step Triggered\033[0;0m'.format(self.name))
- elif decoded_message['event'] == 'SequencePreviousStep':
- self.previous_step()
- Logger.log(LOG_LEVEL["info"], 'Sequence {0} Previous Step Triggered\033[0;0m'.format(self.name))
- elif decoded_message['event'] == 'SequenceStart':
- self.start()
- Logger.log(LOG_LEVEL["info"], 'Sequence {0} Start Triggered\033[0;0m'.format(self.name))
- elif decoded_message['event'] == 'SequenceSkipStep':
- self.step_complete = True
- Logger.log(LOG_LEVEL["info"], 'Sequence {0} Skip Step Triggered\033[0;0m'.format(self.name))
- elif decoded_message['event'] == 'SequenceStop':
- self.stop()
- Logger.log(LOG_LEVEL["info"], 'Sequence {0} Stop Triggered\033[0;0m'.format(self.name))
- except:
- Logger.log(LOG_LEVEL["info"], 'Error Decoding Message for Sequence {0}'.format(self.config['key']))
+ def trigger(self, value=None):
+ try:
+ for action in self.sequence[self.current_step].get('actions', []):
+ self.actions[action].trigger(value)
+ self.step_triggered = True
+ except Exception as e:
+ Logger.log(LOG_LEVEL["error"],
+ "Error triggering sequence action {0} ".format(
+ self.key), e)
+ pass
+ return
- def trigger(self, value=None):
- try:
- for action in self.sequence[self.current_step].get('actions', []):
- self.actions[action].trigger(value)
- self.step_triggered = True
- except Exception as e:
- Logger.log(LOG_LEVEL["error"], "Error triggering sequence action {0} ".format(self.key), e)
- pass
- return
+ def evaluate_thresholds(self):
+ thresholds_passed = False
+ if self.sequence[self.current_step].get('thresholds',
+ None) is not None:
+ for threshold in self.sequence[self.current_step].get('thresholds',
+ []):
+ key = threshold.get("source", None)
+ data = self.r.get(key)
+ if data is not None:
+ if threshold.get("nested_source", None) is not None:
+ nested_source = threshold['nested_source'].lower()
+ data = json.loads(data.decode('utf-8'))
+ value = data.get(nested_source,
+ None) if nested_source is not None else data
+ comparison = threshold.get("comparison", "eq")
+ if comparison == "eq":
+ if value == threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+ elif comparison == "ne":
+ if value != threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+ elif comparison == "gt":
+ if value > threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+ elif comparison == "gte":
+ if value >= threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+ elif comparison == "lt":
+ if value < threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+ elif comparison == "lte":
+ if value <= threshold["value"]:
+ thresholds_passed = True
+ else:
+ thresholds_passed = False
+ else:
+ # No thresholds for this step, proceed.
+ thresholds_passed = True
+ return thresholds_passed
- def evaluateThresholds(self):
- thresholds_passed = False
- if self.sequence[self.current_step].get('thresholds', None) is not None:
- for threshold in self.sequence[self.current_step].get('thresholds', []):
- key = threshold.get("source", None)
- data = self.r.get(key)
- if data is not None:
- if threshold.get("nested_source", None) is not None:
- nested_source = threshold['nested_source'].lower()
- data = json.loads(data.decode('utf-8'))
- value = data.get(nested_source, None) if nested_source is not None else data
- comparison = threshold.get("comparison", "eq")
- if comparison == "eq":
- if value == threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "ne":
- if value != threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "gt":
- if value > threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "gte":
- if value >= threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "lt":
- if value < threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- elif comparison == "lte":
- if value <= threshold["value"]:
- thresholds_passed = True
- else:
- thresholds_passed = False
- else:
- # No thresholds for this step, proceed.
- thresholds_passed = True
- return thresholds_passed
+ def wait(self, duration=0):
+ time_remaining = duration
+ while time_remaining > 0 and self.main_thread_running.is_set():
+ self.pubsub.get_message()
+ time.sleep(1)
+ time_remaining -= 1
- def wait(self, duration=0):
- time_remaining = duration
- while time_remaining > 0 and self.main_thread_running.is_set():
- self.pubsub.get_message()
- time.sleep(1)
- time_remaining-=1
+ def run(self):
+ Logger.log(LOG_LEVEL["info"], 'Sequence Worker [' + str(
+ self.name) + ']...\t\033[1;32m Online\033[0;0m')
+ return super().run()
- def run(self):
- Logger.log(LOG_LEVEL["info"], 'Sequence Worker [' + str(self.name) + ']...\t\033[1;32m Online\033[0;0m')
- return super().run()
+ def work(self):
+ self.reset_elapsed_time()
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ try:
+ self.pubsub.get_message()
+ if self.sequence_available.is_set():
+ if self.sequence_active.is_set():
+ while not self.step_complete:
+ if not self.delay_complete:
+ if self.sequence[self.current_step].get(
+ 'delay', None) is not None:
+ self.wait(int(self.sequence[
+ self.current_step].get(
+ 'delay', 0)))
+ self.delay_complete = True
+ else:
+ # No Delay for this step
+ self.delay_complete = True
+ if not self.step_triggered:
+ if self.delay_complete:
+ if self.evaluate_thresholds():
+ self.trigger()
+ else:
+ if self.sequence[
+ self.current_step].get(
+ 'thresholds',
+ None) is not None:
+ # Thresholds failed skip step waiting
+ self.step_complete = True
+ if self.sequence[self.current_step].get(
+ 'duration',
+ None) is not None and not self.step_complete:
+ self.wait(int(
+ self.sequence[self.current_step].get(
+ 'duration', 0)))
+ self.step_complete = True
+ time.sleep(1)
+ if self.step_complete:
+ self.r.publish(self.topic, json.dumps({
+ "event": "SequenceStepEnded",
+ "data": {
+ "name": self.name,
+ "key": self.key,
+ "step": self.current_step
+ }
+ }))
+ self.next_step()
+ else:
+ # Sequence not active and waiting to start
+ time.sleep(1)
+ else:
+ # Sequence Disabled
+ time.sleep(1)
+ except Exception as e:
+ Logger.log(LOG_LEVEL["error"],
+ "Sequence Worker {0} \t\033[1;31m Unexpected Error\033[0;0m".format(
+ self.key))
+ Logger.log(LOG_LEVEL["error"], e)
+ time.sleep(3)
+ else:
+ # System not ready
+ time.sleep(1)
+ self.reset_elapsed_time()
- def work(self):
- self.resetElapsedTime()
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- try:
- self.pubsub.get_message()
- if self.sequence_available.is_set():
- if self.sequence_active.is_set():
- while not self.step_complete:
- if not self.delay_complete:
- if self.sequence[self.current_step].get('delay', None) is not None:
- self.wait(int(self.sequence[self.current_step].get('delay', 0)))
- self.delay_complete = True
- else:
- # No Delay for this step
- self.delay_complete = True
- if not self.step_triggered:
- if self.delay_complete:
- if self.evaluateThresholds():
- self.trigger()
- else:
- if self.sequence[self.current_step].get('thresholds', None) is not None:
- # Thresholds failed skip step waiting
- self.step_complete = True
- if self.sequence[self.current_step].get('duration', None) is not None and not self.step_complete:
- self.wait(int(self.sequence[self.current_step].get('duration', 0)))
- self.step_complete = True
- time.sleep(1)
- if self.step_complete:
- self.r.publish(self.topic, json.dumps({
- "event": "SequenceStepEnded",
- "data": {
- "name": self.name,
- "key": self.key,
- "step": self.current_step
- }
- }))
- self.next_step()
- else:
- # Sequence not active and waiting to start
- time.sleep(1)
- else:
- # Sequence Disabled
- time.sleep(1)
- except Exception as e:
- Logger.log(LOG_LEVEL["error"], "Sequence Worker {0} \t\033[1;31m Unexpected Error\033[0;0m".format(self.key))
- Logger.log(LOG_LEVEL["error"], e)
- time.sleep(3)
- else:
- # System not ready
- time.sleep(1)
- self.resetElapsedTime()
-
- time.sleep(0.1)
+ time.sleep(0.1)
- # This is only ran after the main thread is shut down
- # Close the pubsub connection
- self.pubsub.close()
- Logger.log(LOG_LEVEL["info"], "Sequence Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
\ No newline at end of file
+ # This is only ran after the main thread is shut down
+ # Close the pubsub connection
+ self.pubsub.close()
+ Logger.log(LOG_LEVEL["info"],
+ "Sequence Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
diff --git a/workers/trigger_worker.py b/workers/trigger_worker.py
index c36fc2e..57a1de3 100644
--- a/workers/trigger_worker.py
+++ b/workers/trigger_worker.py
@@ -5,136 +5,150 @@
import threading
from .worker import Worker
import sys
-sys.path.append('..')
+
+
from triggers.time_trigger import TimeTrigger
from triggers.trigger_group import TriggerGroup
from triggers.sensor_trigger import SensorTrigger
from triggers.control_trigger import ControlTrigger
-import variables
+import constants
import importlib
from logger.Logger import Logger, LOG_LEVEL
+
class TriggerWorker(Worker):
- def __init__(self, config, main_thread_running, system_ready, actions, sequences):
- super().__init__(config, main_thread_running, system_ready)
- self.actions = actions
- self.sequences = sequences
- self.triggers = []
- self.trigger_threads = []
- self.trigger_events = {}
- self.init()
- return
-
- def init(self):
- trigger_index = 0
- for trigger in self.config:
- if trigger.get("triggers", False):
- # Load a trigger group
-
- trigger_actions = []
- if trigger.get('actions'):
- for action in trigger.get("actions"):
- trigger_actions.append(self.actions[action])
-
- new_trigger_group = TriggerGroup(name=trigger.get("group"), key=trigger.get("key"), 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
-
- def init_trigger(self, config, trigger_index, group=None):
- if config.get('type', None) is not None:
- # Get the trigger from the triggers folder triggers/{trigger type}_trigger.py
- trigger_type = 'triggers.' + config.get('type').lower() + '_trigger.' + config.get('type').capitalize() + 'Trigger'
-
- imported_trigger = self.dynamic_import(trigger_type)
-
- trigger_state = {
- "active": threading.Event() # Event to signal if trigger is active
- }
-
- self.trigger_events[config.get("key", trigger_index)] = trigger_state
-
- # Define default kwargs for all trigger types, conditionally include optional variables below if they exist
- trigger_kwargs = {
- 'name' : config.get('name', None),
- 'key' : config.get('key', None),
- 'trigger_active' : trigger_state["active"],
- 'main_thread_running' : self.main_thread_running,
- 'system_ready' : self.system_ready
- }
-
- # 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 config.get('sequences'):
- trigger_sequences = []
- for sequence in config.get("sequences"):
- trigger_sequences.append(self.sequences[sequence])
- trigger_kwargs['sequences'] = trigger_sequences
-
- if config.get('frequency'):
- trigger_kwargs['frequency'] = config.get('frequency')
-
- if config.get('schedule'):
- trigger_kwargs['schedule'] = config.get('schedule')
-
- if config.get('source'):
- trigger_kwargs['source'] = config.get('source')
-
- if config.get('nested_source'):
- trigger_kwargs['nested_source'] = config.get('nested_source')
-
- if config.get('topic'):
- trigger_kwargs['topic'] = config.get('topic')
-
- if config.get('thresholds'):
- trigger_kwargs['thresholds'] = config.get('thresholds')
-
- if group is not None:
- trigger_kwargs['group'] = group
-
- new_trigger = imported_trigger(**trigger_kwargs)
- new_trigger.init_trigger()
-
- new_trigger.type = config.get('type').lower()
-
- return new_trigger
-
- def run(self):
- Logger.log(LOG_LEVEL["info"], 'Trigger Worker [' + str(len(self.config)) + ' Triggers]...\t\t\033[1;32m Online\033[0;0m')
- return super().run()
-
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- # Main Loop
- time.sleep(1)
-
- time.sleep(2)
- # This is only ran after the main thread is shut down
- for trigger in self.triggers:
- trigger.shutdown()
- # Join all our sub threads for shutdown
- for thread in self.trigger_threads:
- thread.join()
- Logger.log(LOG_LEVEL["info"], "Trigger Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m")
\ No newline at end of file
+ def __init__(self, config, main_thread_running, system_ready, actions,
+ sequences):
+ super().__init__(config, main_thread_running, system_ready)
+ self.actions = actions
+ self.sequences = sequences
+ self.triggers = []
+ self.trigger_threads = []
+ self.trigger_events = {}
+ self.init()
+ return
+
+ def init(self):
+ trigger_index = 0
+ for trigger in self.config:
+ if trigger.get("triggers", False):
+ # Load a trigger group
+
+ trigger_actions = []
+ if trigger.get('actions'):
+ for action in trigger.get("actions"):
+ trigger_actions.append(self.actions[action])
+
+ new_trigger_group = TriggerGroup(name=trigger.get("group"),
+ key=trigger.get("key"),
+ 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
+
+ def init_trigger(self, config, trigger_index, group=None):
+ if config.get('type', None) is not None:
+ # Get the trigger from the triggers folder triggers/{trigger type}_trigger.py
+ trigger_type = 'triggers.' + config.get(
+ 'type').lower() + '_trigger.' + config.get(
+ 'type').capitalize() + 'Trigger'
+
+ imported_trigger = self.dynamic_import(trigger_type)
+
+ trigger_state = {
+ "active": threading.Event()
+ # Event to signal if trigger is active
+ }
+
+ self.trigger_events[
+ config.get("key", trigger_index)] = trigger_state
+
+ # Define default kwargs for all trigger types, conditionally include optional variables below if they exist
+ trigger_kwargs = {
+ 'name': config.get('name', None),
+ 'key': config.get('key', None),
+ 'trigger_active': trigger_state["active"],
+ 'main_thread_running': self.main_thread_running,
+ 'system_ready': self.system_ready
+ }
+
+ # 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 config.get('sequences'):
+ trigger_sequences = []
+ for sequence in config.get("sequences"):
+ trigger_sequences.append(self.sequences[sequence])
+ trigger_kwargs['sequences'] = trigger_sequences
+
+ if config.get('frequency'):
+ trigger_kwargs['frequency'] = config.get('frequency')
+
+ if config.get('schedule'):
+ trigger_kwargs['schedule'] = config.get('schedule')
+
+ if config.get('source'):
+ trigger_kwargs['source'] = config.get('source')
+
+ if config.get('nested_source'):
+ trigger_kwargs['nested_source'] = config.get('nested_source')
+
+ if config.get('topic'):
+ trigger_kwargs['topic'] = config.get('topic')
+
+ if config.get('thresholds'):
+ trigger_kwargs['thresholds'] = config.get('thresholds')
+
+ if group is not None:
+ trigger_kwargs['group'] = group
+
+ new_trigger = imported_trigger(**trigger_kwargs)
+ new_trigger.init_trigger()
+
+ new_trigger.type = config.get('type').lower()
+
+ return new_trigger
+
+ def run(self):
+ Logger.log(LOG_LEVEL["info"], 'Trigger Worker [' + str(
+ len(self.config)) + ' Triggers]...\t\t\033[1;32m Online\033[0;0m')
+ return super().run()
+
+ def work(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ # Main Loop
+ time.sleep(1)
+
+ time.sleep(2)
+ # This is only ran after the main thread is shut down
+ for trigger in self.triggers:
+ trigger.shutdown()
+ # Join all our sub threads for shutdown
+ for thread in self.trigger_threads:
+ thread.join()
+ Logger.log(LOG_LEVEL["info"],
+ "Trigger Worker Shutting Down...\t\t\033[1;32m Complete\033[0;0m")
diff --git a/workers/worker.py b/workers/worker.py
index a5da114..b1f91c7 100644
--- a/workers/worker.py
+++ b/workers/worker.py
@@ -4,70 +4,78 @@
import redis
import threading
import sys
-sys.path.append('..')
-import variables
+
+
+import constants
from logger.Logger import Logger, LOG_LEVEL
-# Base Worker Class
-# A worker is responsible for handling its set of operations and running on a thread
-class Worker():
- def __init__(self, config, main_thread_running, system_ready):
- self.config = config
- # Threading Events to Keep Everything in Sync
- self.main_thread_running = main_thread_running
- self.system_ready = system_ready
- self.worker_available = threading.Event()
- self.components = []
- return
+class Worker:
+ """Base Worker Class
+
+ A worker is responsible for handling its set of operations and
+ running on a thread
+ """
+
+ def __init__(self, config, main_thread_running, system_ready):
+ self.config = config
+ # Threading Events to Keep Everything in Sync
+ self.main_thread_running = main_thread_running
+ self.system_ready = system_ready
+ self.worker_available = threading.Event()
+
+ self.components = []
+ return
- def init(self):
- # print('Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(**control))
- return
+ def init(self):
+ # print('Worker...\t\t\t\033[1;32m Initializing\033[0;0m'.format(**control))
+ return
- def run(self):
- t = threading.Thread(target=self.work, args=())
- t.start()
- return t
+ def run(self):
+ t = threading.Thread(target=self.work, args=())
+ t.start()
+ return t
- def work(self):
- while self.main_thread_running.is_set():
- if self.system_ready.is_set():
- time.sleep(self.sleep_duration)
- #This is only ran after the main thread is shut down
- Logger.log(LOG_LEVEL["info"], "Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
+ def work(self):
+ while self.main_thread_running.is_set():
+ if self.system_ready.is_set():
+ time.sleep(self.sleep_duration)
+ # This is only ran after the main thread is shut down
+ Logger.log(LOG_LEVEL["info"],
+ "Worker Shutting Down...\t\033[1;32m Complete\033[0;0m")
- def elapsedTime(self):
- self.time_elapsed = time.perf_counter() - self.time_start
- return self.time_elapsed
+ def elapsed_time(self):
+ self.time_elapsed = time.perf_counter() - self.time_start
+ return self.time_elapsed
- def resetElapsedTime(self):
- self.time_start = time.perf_counter()
- pass
+ def reset_elapsed_time(self):
+ self.time_start = time.perf_counter()
+ pass
- def dynamic_import(self, name):
- # Split path of the class folder structure: {sensor name}_sensor . {SensorName}Sensor
- components = name.split('.')
- # Dynamically import root of component path
- module = __import__(components[0])
- # Get component attributes
- for component in components[1:]:
- module = getattr(module, component)
- return module
+ def dynamic_import(self, name):
+ # Split path of the class folder structure:
+ # {sensor name}_sensor . {SensorName}Sensor
+ components = name.split('.')
+ # Dynamically import root of component path
+ module = __import__(components[0])
+ # Get component attributes
+ for component in components[1:]:
+ module = getattr(module, component)
+ return module
- def decodeMessageData(self, message):
- if isinstance(message, dict):
- #print('Dict Found')
- return message
- elif isinstance(message.decode('utf-8'), str):
- try:
- temp = json.loads(message.decode('utf-8'))
- #print('Json Found')
- return temp
- except:
- #print('Json Error. Str Found')
- return {'event':'Unknown', 'data':message}
- else:
- #print('Failed to detect type')
- return {'event':'Unknown', 'data':message}
\ No newline at end of file
+ def decode_message_data(self, message):
+ if isinstance(message, dict):
+ # print('Dict Found')
+ return message
+ elif isinstance(message.decode('utf-8'), str):
+ try:
+ temp = json.loads(message.decode('utf-8'))
+ # print('Json Found')
+ return temp
+ except:
+ # print('Json Error. Str Found')
+ return {'event': 'Unknown', 'data': message}
+ else:
+ # print('Failed to detect type')
+ return {'event': 'Unknown', 'data': message}