commit b2ad105079d19e5054f2106aa2a0b5b3c561bdb8 Author: Thomas Klaehn Date: Mon Apr 8 17:26:45 2019 +0200 greenhouse: Initial commit diff --git a/greenhouse.service b/greenhouse.service new file mode 100644 index 0000000..a2171f9 --- /dev/null +++ b/greenhouse.service @@ -0,0 +1,10 @@ + [Unit] + Description=Greenhouse service + After=multi-user.target + + [Service] + Type=idle + ExecStart=/usr/bin/python3 -m greenhouse + + [Install] + WantedBy=multi-user.target \ No newline at end of file diff --git a/greenhouse/__init__.py b/greenhouse/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/greenhouse/__main__.py b/greenhouse/__main__.py new file mode 100644 index 0000000..abecdac --- /dev/null +++ b/greenhouse/__main__.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + +import datetime +import logging +import sys +import threading +import time + +import RPi.GPIO as gpio +import paho.mqtt.client as mqtt +import Adafruit_DHT + +SWITCH_ON_TEMP = 8.0 +SWITCH_OFF_TEMP = 10.0 + +log_level = logging.INFO +LOG_FILE = "/var/log/greenhouse.log" +LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s" + +#logging.basicConfig(format=LOG_FORMAT, level=log_level, filename=LOG_FILE) +logging.basicConfig(format=LOG_FORMAT, level=log_level) +log = logging.getLogger('greenhouse') + +class EnvData(object): + def __init__(self): + self.stamp = datetime.datetime.fromtimestamp(0) + self.temperature = -273.15 + self.humidity = -1.0 + self.is_valid = False + + +class EnvDataCollector(threading.Thread): + def __init__(self, name, dht_pin, update_interval=10): + super(EnvDataCollector, self).__init__() + self.name = name + self.sensor = 22 + self.dht_pin = dht_pin + self.update_interval = update_interval + self.run_condition = True + self.data = EnvData() + + def run(self): + next_update = datetime.datetime.now() + while self.run_condition: + now = datetime.datetime.now() + if now >= next_update: + humidity, temperature = Adafruit_DHT.read(self.sensor, self.dht_pin) + if humidity is None or temperature is None: + log.info("%s: No data read - setting re-read intterval to 1s.", self.name) + next_update = now + datetime.timedelta(seconds=1) + else: + if humidity >= 0.0 and humidity <= 100.0 and temperature >= -273.15: + self.data.temperature = temperature + self.data.humidity = humidity + self.data.stamp = now + self.data.is_valid = True + next_update = now + datetime.timedelta(seconds=self.update_interval) + log.info("%s: Data read - setting re-read intterval to %ss.", + self.name, self.update_interval) + else: + log.info("%s: Invalid data read - setting re-read intterval to 1s.", + self.name) + next_update = now + datetime.timedelta(seconds=1) + time.sleep(1) + + def stop(self): + self.run_condition = False + self.join() + + +class MqttHandler(object): + def __init__(self, url, port, certificate): + self.url = url + self.port = port + self.certificate = certificate + self.client = mqtt.Client() + self.client.tls_set(certificate) + self.client.connect(url, port, 60) + self.client.loop_start() + + def stop(self): + self.client.loop_stop() + self.client.disconnect() + + def transmit(self, topic, message): + self.client.publish(topic, message, qos=2, retain=True) + + +class Heat(object): + def __init__(self, pin): + self.pin = pin + gpio.setwarnings(False) + gpio.setmode(gpio.BCM) + gpio.setup(pin, gpio.OUT) + + def on(self): + gpio.output(self.pin, 0) + + def off(self): + gpio.output(self.pin, 1) + + +def main(): + old_green = EnvDataCollector("Greenhouse one", 17) + old_heat = Heat(6) + old_heat_state = "off" + new_green = EnvDataCollector("Greenhouse two", 21) + old_green.start() + new_green.start() + mqtt_handler = MqttHandler("mqtt.blackfinn.de", 8883, "/etc/ssl/certs/DST_Root_CA_X3.pem") + try: + next_update = datetime.datetime.now() + while True: + now = datetime.datetime.now() + if now >= next_update: + + # 1) Check timestamp of last data collection. If data are older + # than 5 minutes the sensor isn't working anymore. + if old_green.data.is_valid and old_green.data.stamp + datetime.timedelta(minutes=5) >= now: + msg = "{} {}".format(datetime.datetime.timestamp(old_green.data.stamp), old_green.data.temperature) + mqtt_handler.transmit("outdoor/greenhouse/temperature", msg) + log.info("%s: Temperature: %s", old_green.data.stamp.time(), old_green.data.temperature) + msg = "{} {}".format(datetime.datetime.timestamp(old_green.data.stamp), old_green.data.humidity) + mqtt_handler.transmit("outdoor/greenhouse/humidity", msg) + log.info("%s: Humidity: %s", old_green.data.stamp.time(), old_green.data.humidity) + + if old_green.data.temperature < SWITCH_ON_TEMP and old_heat_state == "off": + old_heat.on() + old_heat_state = "on" + msg = "{} {}".format(time.time(), old_heat_state) + mqtt_handler.transmit("outdoor/greenhouse/heat", msg) + log.info("%s: Heat: switch %s", time.time(), old_heat_state) + elif old_green.data.temperature > SWITCH_OFF_TEMP and old_heat_state == "on": + old_heat.off() + old_heat_state = "off" + msg = "{} {}".format(time.time(), old_heat_state) + mqtt_handler.transmit("outdoor/greenhouse/heat", msg) + log.info("%s: Heat: switch %s", time.time(), old_heat_state) + next_update = now + datetime.timedelta(minutes=15) + else: + msg = "{} Error! Last valid DHT sensor data is from {}.".format(time.time(), old_green.data.stamp) + mqtt_handler.transmit("outdoor/greenhouse/status", msg) + log.error("%s: Error! Last valid DHT sensor data is from %s", time.time(), old_green.data.stamp) + next_update = now + datetime.timedelta(seconds=5) + time.sleep(1) + except KeyboardInterrupt: + log.info("Shutting down...") + old_green.stop() + new_green.stop() + mqtt_handler.stop() + log.info("Exiting...") + +if __name__ == "__main__": + sys.exit(main()) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..726c8cb --- /dev/null +++ b/setup.py @@ -0,0 +1,28 @@ +#!/usr/bin/python3 +''' +@author: Thomas Klaehn +''' +from distutils.core import setup +import os +import shutil +import stat +import sys + +NAME = 'greenhouse' +VERSION = '1.0.0' +AUTHOR = 'Thomas Klaehn' +EMAIL = 'tkl@blackfinn.de' +SYSTEMD_SCRIPTS = ['greenhouse.service'] +PACKAGES = ['greenhouse'] +PACKAGE_DIRS = {'greenhouse':'greenhouse'} + +SYSTEMD_PATH = '/lib/systemd/system/' + +if sys.argv[1] == 'install': + for script in SYSTEMD_SCRIPTS: + shutil.copyfile(script, os.path.join(SYSTEMD_PATH, script)) + os.chmod(os.path.join(SYSTEMD_PATH, script), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) + setup(name=NAME, version=VERSION, author=AUTHOR, author_email=EMAIL, package_dir=PACKAGE_DIRS, packages=PACKAGES) +elif sys.argv[1] == 'sdist': + setup(name=NAME, version=VERSION, author=AUTHOR, author_email=EMAIL, package_dir=PACKAGE_DIRS, packages=PACKAGES, + scripts=SYSTEMD_SCRIPTS)