Raspberry pi automatische Bewässerung

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
ich1875
User
Beiträge: 3
Registriert: Sonntag 3. Mai 2020, 10:12

Hallo liebe Leute :),

ich in recht neu in der Sprache und habe ein Kleine Projekt für meine Büropflanzen rausgesucht, um diese zu bewässern.

Leider erhalte ich derzeit folgende Fehlermeldung:
---------------------------------------------------------------------------------------------------------------------------------------
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
self.run()
File "Neu.py", line 36, in run
humidity = self.get_sync_humidity(self.potconfig['sensorchannel'], self.potconfig['sensorgpio'])
TypeError: 'NoneType' object has no attribute '__getitem__'
-------------------------------------------------------------------------------------------------------------------------------------------


Mein Skript selber sieht wie folgt aus:
-------------------------------------------------------------------------------------------------------------------------------------------
import datetime
import time
import Adafruit_GPIO.SPI as SPI
import Adafruit_MCP3008
import RPi.GPIO as GPIO
import threading
import yaml
import pprint
import smtplib
from influxdb import InfluxDBClient

class PotThread(threading.Thread):
#args ist ein pot-dict
def __init__(self, group=None, monitoronly=False, influxclient=None, target=None, threadname=None, debug=None, args=()):
threading.Thread.__init__(self, group=group, target=target, name=threadname)
if 'pot' in args:
self.potconfig=args['pot']
else:
self.potconfig={}
if threadname:
self.threadname=threadname
else:
if self.potconfig and "name" in self.potconfig:
self.threadname = self.potconfig['name']
else:
self.threadname = "Unknown"
self.debug = debug
self.active = True
self.influxclient = influxclient
self.monitoronly = monitoronly

def run(self):
measurements = []
while True:
if self.active:
humidity = self.get_sync_humidity(self.potconfig['sensorchannel'], self.potconfig['sensorgpio'])
if self.debug:
print (str(datetime.datetime.now())+" "+self.threadname+" Humidity: "+str(humidity))
if self.influxclient:
measurement = {
'measurement': 'humidity',
'tags': {
'name': self.threadname
},
'time' : time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
'fields': {
'level':humidity
}
}
measurements.append(measurement)
try:
self.influxclient.write_points(measurements)
measurements=[]
except:
print ("Influx failed for "+self.threadname)

if humidity > int(self.potconfig['schwellwert']):
if self.debug:
print (str(datetime.datetime.now())+" "+self.threadname+" Pump on ")
self.pump_on(self.potconfig['pumpsekunden'], self.potconfig['relaisgpio'])
if self.debug:
print (str(datetime.datetime.now())+" "+self.threadname+" Pump off ")

time.sleep(self.potconfig['messpause2'])
humidity2 = self.get_sync_humidity(self.potconfig['sensorchannel'], self.potconfig['sensorgpio'])
if humidity2 > int(self.potconfig['schwellwert2']):
if self.debug:
print (str(datetime.datetime.now())+" "+self.threadname+" Pump on ")
self.pump_on(self.potconfig['pumpsekunden2'], self.potconfig['relaisgpio'])
if self.debug:
print (str(datetime.datetime.now())+" "+self.threadname+" Pump off ")

time.sleep(self.potconfig['messpause'])

def pump_on(self, seconds, gpio):
if not self.monitoronly:
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(gpio, GPIO.OUT)
GPIO.output(gpio, 0)
time.sleep(int(seconds))
GPIO.output(gpio, 1)

def get_sync_humidity(self, sensorchannel, sensorgpio):
SPI_PORT = 0
SPI_DEVICE = 0
lock = threading.RLock()
lock.acquire()
mcp = Adafruit_MCP3008.MCP3008(spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE))
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(sensorgpio, GPIO.OUT)
GPIO.output(sensorgpio, 1)
time.sleep(3)
hum = mcp.read_adc(sensorchannel)
GPIO.output(sensorgpio, 0)
mcp._spi.close()
lock.release()
return(hum)

def set_active(self, active):
if self.debug:
print (self.threadname+" Setting active to: "+str(active))
self.active = active

if __name__ == '__main__':

configfile = open("config.yml", "r")
configyml = configfile.read()
configfile.close()
config=yaml.load(configyml, Loader=yaml.Loader)
influxclient = None
if "influx" in config:
influxclient = InfluxDBClient(config['influx']['server'], 8086, config['influx']['user'], config['influx']['password'], config['influx']['database'])
debug = config['debug']
monitoronly = False
if "monitoronly" in config:
monitoronly = config["monitoronly"]
children = []
for pot in config['pots']:
pt = PotThread(debug=debug, influxclient=influxclient, monitoronly=monitoronly, args=(pot))
pt.start()
children.append(pt)

tankpot = {}
tankthread = PotThread(monitoronly=True, debug=debug, args=(tankpot))
while True:
tankhum = tankthread.get_sync_humidity(config['tank']['sensorchannel'], config['tank']['sensorgpio'])
if debug:
print ("Tank: "+str(tankhum))
if tankhum > config['tank']['schwellwert']:
mailserver = smtplib.SMTP(config['mail']['server'])
mailserver.sendmail(config['mail']['from'], config['mail']['to'], "Please fill water tank")
mailserver.quit()
print ("Please fill tank")
for pot in children:
pot.set_active(False)
else:
for pot in children:
pot.set_active(True)

time.sleep(config['tank']['messpause'])
-------------------------------------------------------------------------------------------------------------------------------------------

Ich hoffe mir kann da einer weiterhelfen :)

Vielen Dank
Benutzeravatar
DeaD_EyE
User
Beiträge: 1206
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Erst, wenn du deinen Code in Code-Tags packst.

Schau mal wo du self.potconfig={} zuweist.
Du greifst auf die Keys: "sensorchannel" und "sensorgpio" zu.
Wo werden die zugewiesen?
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
ich1875
User
Beiträge: 3
Registriert: Sonntag 3. Mai 2020, 10:12

Hallo DeaD_Eye,

ich habe die Werte eigentlich in der Config.yml hinterlegt:

Code: Alles auswählen

pots:
  - pot:
    sensorchannel: 1
    sensorgpio: 19
    pumpsekunden: 15
    pumpsekunden2: 5
    schwellwert: 825
    schwellwert2: 805
    messpause: 60
    messpause2: 1200
    relaisgpio: 16
    name: ficus
  - pot:
    sensorchannel: 2
    sensorgpio: 26
    pumpsekunden: 10
    pumpsekunden2: 5
    schwellwert: 825
    schwellwert2: 785
    messpause: 60
    messpause2: 1200
    relaisgpio: 20
    name: orchid
  - pot:
    sensorchannel: 3
    sensorgpio: 13
    pumpsekunden: 10
    pumpsekunden2: 5
    schwellwert: 825
    schwellwert2: 785
    messpause: 60
    messpause2: 1200
    relaisgpio: 21
    name: dickblatt

tank:
  sensorchannel: 0
  sensorgpio: 17
  messpause: 7200
  schwellwert: 830

mail:
  server: 1.2.3.4
  from: watering@local
  to: gaertner@local

influx:
  server: 2.3.4.5
  user: garten
  password: gartenpw
  database: gartendb

debug: True
monitoronly: True
Grüße
ich1875
User
Beiträge: 3
Registriert: Sonntag 3. Mai 2020, 10:12

Hmm :roll:

kann es sein, dass es das ich diesen part voranstellen muss:

Code: Alles auswählen

if __name__ == '__getitem__':
  configfile = open("config.yml", "r")
  configyml = configfile.read()
  configfile.close()
  config=yaml.load(configyml, Loader=yaml.Loader)
Grüße
Sirius3
User
Beiträge: 18227
Registriert: Sonntag 21. Oktober 2012, 17:20

Eingerückt wird mit 4 Leerzeichen pro Ebene, nicht 2. Die vielen Debug-Ausgaben machen den Code auch nicht gerade lesbarer. Dafür gibt es logging. Strings stückelt man nicht mit + zusammen, sondern benutzt String-Formatierung.
Bei GPIOs aus verschiedenen Threads zuzugreifen, halte ich ja für gefährlich.
GPIO-Warnungen sind dazu da, dass man sie behebt, nicht dass man sie ignoriert. Dazu muß man auch verläßlich GPIO.cleanup aufrufen. Man initalisiert die GPIOs auch einmal am Anfang.
In `get_sync_humidity` macht das Lock ja wenig Sinn, wenn es nie benutzt wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ich1875: `pprint` wird importiert aber nicht verwendet. ``as`` bei Importen ist zum Umbenennen da, es wird aber in beiden Fällen gar nichts umbenannt.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Der ganze Code im ``if __name__ …``-Zweig definiert Variablen im Modul die da nicht hingehören und sollte in einer Funktion stehen.

Es fehlt ein `GPIO.cleanup()`-Aufruf der sichergestellt am des Programms ausgeführt wird. Dann braucht man auch keine Warnungen unterdrücken. Deren Ursache sollte man beseitigen. Den Board-Modus braucht man nur einmal setzen.

Dateien sollte man wo möglich mit der ``with``-Anweisung öffnen und bei Textdateien immer eine konkrete Kodierung angeben. Bei YAML-Dateien würde ich aber die Datei im Binärmodus öffnen und dem YAML-Modul überlassen herauszufinden ob die Datei UTF-8 oder UTF-16 kodiert ist. `yaml.load()` kann man auch ein Dateiobjekt übergeben, man muss den Inhalt nicht selbst vorher komplett einlesen.

Ich vermisse bei so einigen Namen Unterstriche zwischen den Worten. WennmanWorteohneTrennerzusammenfügtsindsieschwererzulesen.

Namen sollten auch keine kryptischen Abkürzungen enthalten oder gar nur aus solchen bestehen. Der Tank summt ja nicht (`tankhum`) und wenn man `pot_thread` meint, sollte man nicht `pt` schreiben.

Wörterbücher haben eine ganz praktische `get()`-Methode mit der man aus dem hier:

Code: Alles auswählen

    monitor_only = False
    if "monitor_only" in config:
        monitor_only = config["monitor_only"]
Diesen Einzeiler machen kann:

Code: Alles auswählen

    monitor_only = config.get("monitor_only", False)
Beim erstellen von `PotThread`-Objekten sind unnötige Klammern um den Wert des `args`-Arguments in beiden Aufrufen.

`args` ist hier auch ein sehr generischer Name der zudem auch in der `__init__` von der Basisklasse `Thread` vorkommt, Dein `args` ist aber offenbar was anderes als das `args` in der überschriebenen Methode. Das ist sehr verwirrend.

`PotThread` ist eine eigenartige Klasse. Da wird ein Exemplar von erstellt was anders ist als die anderen und sich anders verhält: das wird nie als Thread gestartet und es wird eine Methode aufgerufen die nichts von dem Objekt verwendet, also eigentlich eine stinknormale Funktion ist, die unabhängig von der Klasse ist.

`target` macht bei einer von Thread abgeleiteten Klasse die `run()` überschreibt keinen Sinn.

`self.threadname` macht keinen Sinn weil der `Thread` von dem man ableitet das ja bereits als Attribut `name` zur Verfügung stellt. Ich würde den auch nicht auf "Unknown" stellen sondern in dem Fall die automatisch vom `threading`-Modul vergebenen Namen verwenden. Dann heissen die Unbekannten nämlich nicht alle gleich und man kann die immer noch auseinanderhalten in Logging-Ausgaben.

Zwischen `print()` und der öffnenden Klammer des Aufrufs gehört kein Leerzeichen. ``return`` ist keine Funktion, sollte also auch nicht so aussehen als wäre es ein Funktionsaufruf – da gehören keine unnötigen Klammern um den Rückgabewert.

Das mit dem `debug` und den `print()`-Aufrufen mit Zeitangaben sieht alles verdammt nach Logging aus, das sollte man nicht selbst noch mal erfinden.

Zeichenketten und Werte mit ``+`` und `str()` zusammensetzen ist eher BASIC denn Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode und ab Python 3.6 f-Zeichenkettenliterale.

Pump on/off wird jeweils vor und nach dem Aufruf der `pump_on()`-Methode aufgerufen – das wäre also besser *in* dieser Methode aufgehoben damit man den Code nicht doppelt hat. Und vielleicht sollte man die dort dann auch nur dann ausgeben wenn die Pumpe tatsächlich ein- und ausgeschaltet wird.

`active` würde ich als Property lösen, damit man keine relativ triviale `set_active()`-Methode braucht. Wobei das simple Flag in der `run()`-Methode dafür sorgt, dass ausgerechnet wenn es `False` ist, der Prozessor völlig ungebremst und unnütz Rechenzeit verbrennt. Und das für jeden `PotThread`, die ja immer alle zusammen inaktiv geschaltet werden.

`SMTP`-Objekte sind Kontextmanager, können und sollten also mit der ``with``-Anweisung verwendet werden. Das gilt auch für Lock-Objekte.

Das Lock in der Feuchtigkeitsabfrage ist fehlerhaft. Wenn man da bei jedem Aufruf ein Neues erstellt, dann hat das letztlich überhaupt keine Wirkung. Es muss auch kein `RLock` sein, ein einfaches `Lock` reicht.

Man sollte nicht auf nicht-öffentliche Attribute zugreifen. Man kann sich das `SpiDev`-Objekt ja auch problemlos ausserhalb des `MCP3008`-Objekts merken. Und auch hier bietet sich wieder die ``with``-Anweisung an. Da die `SpiDev`-Objekte *keine* Kontextmanager sind, käme da dann `contextlib.closing()` zur Anwendung.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import smtplib
import sys
import threading
import time
from contextlib import closing
from datetime import datetime as DateTime

import Adafruit_MCP3008
import yaml
from Adafruit_GPIO import SPI
from influxdb import InfluxDBClient
from loguru import logger
from RPi import GPIO

SPI_PORT = 0
SPI_DEVICE = 0


def get_humidity(sensor_channel, sensor_gpio, lock=threading.Lock()):
    with lock:
        with closing(SPI.SpiDev(SPI_PORT, SPI_DEVICE)) as spi_device:
            mcp = Adafruit_MCP3008.MCP3008(spi=spi_device)
            GPIO.setup(sensor_gpio, GPIO.OUT)
            GPIO.output(sensor_gpio, 1)
            time.sleep(3)
            humidity = mcp.read_adc(sensor_channel)
            GPIO.output(sensor_gpio, 0)
    return humidity


class PotThread(threading.Thread):
    def __init__(self, monitor_only, influx_client, pot_config):
        self.pot_config = pot_config
        threading.Thread.__init__(self, name=self.pot_config.get("name"))
        self._active_event = threading.Event()
        self._active_event.set()
        self.influx_client = influx_client
        self.monitor_only = monitor_only

    @property
    def active(self):
        return self._active_event.is_set()

    @active.setter
    def active(self, value):
        logger.debug("Setting active to {}", value)
        if value:
            self._active_event.set()
        else:
            self._active_event.clear()

    @logger.catch()
    def run(self):
        measurements = []
        while True:
            self._active_event.wait()
            while self._active_event.is_set():
                humidity = get_humidity(
                    self.pot_config["sensor_channel"],
                    self.pot_config["sensor_gpio"],
                )
                logger.debug("Humidity: {}", humidity)
                if self.influx_client:
                    measurements.append(
                        {
                            "measurement": "humidity",
                            "tags": {"name": self.name},
                            "time": format(
                                DateTime.utcnow(), "%Y-%m-%dT%H:%M:%SZ"
                            ),
                            "fields": {"level": humidity},
                        }
                    )
                    try:
                        self.influx_client.write_points(measurements)
                        measurements = []
                    except:
                        logger.exception("Influx failed")

                if humidity > self.pot_config["schwellwert"]:
                    self.pump_on(
                        self.pot_config["pumpsekunden"],
                        self.pot_config["relais_gpio"],
                    )
                    time.sleep(self.pot_config["messpause2"])
                    humidity = get_humidity(
                        self.pot_config["sensor_channel"],
                        self.pot_config["sensor_gpio"],
                    )
                    if humidity > self.pot_config["schwellwert2"]:
                        self.pump_on(
                            self.pot_config["pumpsekunden2"],
                            self.pot_config["relais_gpio"],
                        )

                time.sleep(self.pot_config["messpause"])

    def pump_on(self, seconds, pin):
        if not self.monitor_only:
            logger.debug("Pump on")
            GPIO.setup(pin, GPIO.OUT)
            GPIO.output(pin, 0)
            time.sleep(seconds)
            GPIO.output(pin, 1)
            logger.debug("Pump off")


@logger.catch()
def main():
    try:
        GPIO.setmode(GPIO.BCM)

        with open("config.yml", "rb") as config_file:
            config = yaml.load(config_file, yaml.Loader)

        debug = config["debug"]
        logger.remove(0)
        logger.add(
            sys.stderr,
            level="DEBUG" if debug else "INFO",
            format=(
                "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
                " | <level>{level: <8}</level>"
                " | <yellow>{thread.name}</yellow>:<cyan>{name}</cyan>"
                ":<cyan>{function}</cyan>:<cyan>{line}</cyan>"
                " - <level>{message}</level>"
            ),
        )

        influx_config = config.get("influx")
        if influx_config:
            influx_client = InfluxDBClient(
                influx_config["server"],
                8086,
                influx_config["user"],
                influx_config["password"],
                influx_config["database"],
            )
        else:
            influx_client = None

        monitor_only = config.get("monitor_only", False)
        pot_threads = []
        for pot in config["pots"]:
            pot_thread = PotThread(monitor_only, influx_client, pot)
            pot_thread.start()
            pot_threads.append(pot_thread)

        while True:
            tank_humidity = get_humidity(
                config["tank"]["sensor_channel"], config["tank"]["sensor_gpio"]
            )
            logger.debug("Tank: {}", tank_humidity)
            is_tank_empty = tank_humidity > config["tank"]["schwellwert"]
            if is_tank_empty:
                with smtplib.SMTP(config["mail"]["server"]) as mailserver:
                    mailserver.sendmail(
                        config["mail"]["from"],
                        config["mail"]["to"],
                        "Please fill water tank.",
                    )
                logger.info("Please fill tank")

            for pot in pot_threads:
                pot.active = not is_tank_empty

            time.sleep(config["tank"]["messpause"])
    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    main()
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten