Severschrank Lüftersteuerung

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Interceptor
User
Beiträge: 3
Registriert: Donnerstag 23. September 2021, 07:13

Hallo Forum.

zunächst vielen Dank für die Aufnahme hier ! :)

Hier die Konfig:

Bei mir ist in meinem Serverschrank ein Raspi 3 B+ verbaut, welcher u.a. die Belüftung des Schrankes temperaturabhängig per Python steuert.
Er gibt dazu auch diverse Daten per LCD Dsplay aus

Folgender Aufbau:
- 4 Noctua 140 mm 4 Pin 12 V
- 2 LED
- Grün: Normalbetrieb
- Rot : Störung eines Lüfters
- 1 Temperatursensor DS18B20
- 1 LCD Diplay LCD1602

Ziel des Python Scripts:

- Erfasse die Temperatur des Sensors
- Erfasse alle 4 Lüfterdrehzahlen
- Schalte bei i.O.-Betrieb die grüne LED und gib aktuelle Temperatur /PWM-Signal / gemittelte DRZ aller 4 Lüfter per Display aus -> gib das entsprechende PWM-Signal an die Lüfter aus
- Steuerung (grob dargestellt) in 4 Stufen:
- < 30 °C Lüfter aus +LED Grün
- >= 30 °C PWM 25% + DRZausgabe+ LED Grün
- >= 34°C PWM 50% + DRZausgabe+ LED Grün
- >= 38 °C PWM 100% +DRZ Max + LED Grün
(Es geht hier sicher auch stufenlos, aber das übersteigt leider mein Wissen ;) !!! )

- Schalte im Fehlerfall die rote LED und gib den ausgefallenen Lüfter per Display aus

Nun zum eigentlichen Problem:

Prio 1:
Das Programm läuft stundenlang und steigt dann mit folgendem Fehr aus:

Code: Alles auswählen

Traceback (most recent call last):
  File "noctua_7.py", line 230, in <module>
    if TemperaturAuswertung() > 30 and TemperaturAuswertung() <= 34:
  File "noctua_7.py", line 58, in TemperaturAuswertung
    while lines[0].strip()[-3:] != 'YES':
IndexError: list index out of range
Ich habe versuch, per Try einen Except mit Indexerror/Continue in die While-Schleife zu bekommen, ich kriegs aber leider nicht gebacken...
Bekomme ständig den Fehler:

Code: Alles auswählen

 'continue' not properly in loop
Bitte helft mir hier, diesen Fehler abzufangen.


Prio 2:
Die Noctua-Lüfter geben ein Rechtecksignal aus, das in Hz und dann in die DRZ umgerechnet werden / gemittelt werden soll.

Das ist mir eher nur diletantisch gelungen...

Bitte helft mir mit der DRZ-Erfassung der Lüfter, ich kanns leider nicht besser.

Ich hänge noch Daten aus dem Datasheet der Lüfter an.


Prio 3:
Das Script ist eine Mischung aus Google , sowie Try & Error...bin leider noch ein blutiger Amateur und war froh, dass ichs überhaupt hinbekommen hab... ;)

Bitte helft mir, das Programm zu entrümpeln, es gibt sicher genuge Codezeile, die überflüssig oder unsauber geschrieben sind...

Hier das Script:

Code: Alles auswählen

a=u"°"
import sys
import time
import datetime
from time import sleep
import termios
import tty
import glob
import RPi.GPIO as GPIO
import numpy as np
import lcddriver
lcd = lcddriver.lcd()
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
signal = 13		#Lüfter 1
signal_2 = 15	#Lüfter 2
signal_3 = 19	#Lüfter 3
signal_4 = 21	#Lüfter 4
led_g = 10		#LED Normalbetrieb
led_r = 12		#LED Störung Lüfter
#GPIO Grundsetup
GPIO.setup(7, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(led_r, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(led_g, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(11, GPIO.OUT, initial=GPIO.LOW)
#'D18B20 Initialisierung
# Nach Aktivierung des Pull-UP Widerstandes wird gewartet,
# bis die Kommunikation mit dem DS18B20 Sensor aufgebaut ist
#print 'Warte auf Initialisierung...'
base_dir = '/sys/bus/w1/devices/'
while True:
    try:
        device_folder = glob.glob(base_dir + '28*')[0]
        break
    except IndexError:
        sleep(0.5)
        continue
device_file = device_folder + '/w1_slave'
#print 'Initialisierung...beendet !'
# Funktion wird definiert, mit dem der aktuelle Messwert am Sensor ausgelesen werden kann
def TemperaturMessung():
    f = open(device_file, 'r')
    lines = f.readlines()
    f.close()
    return lines
# Zur Initialisierung, wird der Sensor einmal "blind" ausgelesen:
TemperaturMessung()
# Die Temperaturauswertung: Beim Raspberry Pi werden erkannte one-Wire Slaves im Ordner
# /sys/bus/w1/devices/ einem eigenen Unterordner zugeordnet. In diesem Ordner befindet sich die Datei w1-slave
# in diesem werden die Daten,welche per One-Wire Bus gesendet wurden gespeichert.
# In dieser Funktion werden diese Daten analysiert,die Temperatur herausgelesen und ausgegeben
def TemperaturAuswertung():
    lines = TemperaturMessung()
    while lines[0].strip()[-3:] != 'YES':
		try:
			time.sleep(0.2)
			lines = TemperaturMessung()
		except IndexError:
			sleep(1)
			continue
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c = float(temp_string) / 1000.0
        return temp_c
#Definition PWM Signal der 4 Gehäuselüfter
luefter_pwm=GPIO.PWM(11,25000)
luefter_pwm.start(0)
#Definition der Eingänge für die Drehzaherfassung der 4 Gehäuselüfter
rpm = 0
rpm11 = 0
rpm1 = 0
rpm12 = 0
rpm3 = 0
rpm13 = 0
rpm4 = 0
rpm14 = 0
rpm_mid = 0
flankenzeit = 0
flankenzeit_2 = 0
flankenzeit_3 = 0
flankenzeit_4 = 0
t = 0
t_2 = 0
t_3 = 0
t_4 = 0
pwms = 0
r = 0
PIO.setup(signal, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(signal_2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(signal_3, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(signal_4, GPIO.IN, pull_up_down=GPIO.PUD_UP)
#Drehzaherfassung:
def puls(n):
	global r
	global t
	global flankenzeit
	global rpm
	global rpm1
	flankenzeit = np.mean(time.time() - t)
	time.sleep(0.001)
	if flankenzeit < 0.001: return
	frequenz = np.mean(1 / flankenzeit)
	rpm = np.average((frequenz / 2) * 60)
	if rpm > 1800: return
	if pwms > 95:
			rpm1 = "%.f" % (rpm,)
	else:
			rpm1 = "%.f" % (np.average(rpm,))
			time.sleep(0.01)
	t = time.time()
GPIO.add_event_detect(signal, GPIO.FALLING, puls)
def puls_2(m):
		global t_2
		global flankenzeit_2
		global rpm11
		global rpm12
		flankenzeit_2 = np.mean(time.time() - t_2)
		time.sleep(0.001)
		if flankenzeit_2 < 0.001: return
		frequenz_2 = np.mean(1 / flankenzeit_2)
		rpm11 = np.average((frequenz_2 / 2) * 60)
		if rpm11 > 1800: return
		if pwms > 95:
			rpm12 = "%.f" % (rpm11,)
		else:
			rpm12 = "%.f" % (np.average(rpm11,))
			time.sleep(0.01)
		t_2 = time.time()
GPIO.add_event_detect(signal_2, GPIO.FALLING, puls_2)
def puls_3(m):
		global t_3
		global flankenzeit_3
		global rpm3
		global rpm13
		flankenzeit_3 = np.mean(time.time() - t_3)
		time.sleep(0.001)
		if flankenzeit_3 < 0.001: return
		frequenz_3 = np.mean(1 / flankenzeit_3)
		rpm3 = np.average((frequenz_3 / 2) * 60)
		if rpm3 > 1800: return
		if pwms > 95:
			rpm13 = "%.f" % (rpm3,)
		else:
			rpm13 = "%.f" % (np.average(rpm3,))
			time.sleep(0.01)
		t_3 = time.time()
GPIO.add_event_detect(signal_3, GPIO.FALLING, puls_3)
def puls_4(m):
	global t_4
	global flankenzeit_4
	global rpm4
	global rpm14
	global rpm_mid
	flankenzeit_4 = np.mean(time.time() - t_4)
	time.sleep(0.001)
	if flankenzeit_4 < 0.001: return
	frequenz_4 = np.mean(1 / flankenzeit_4)
	rpm4 = np.average((frequenz_4 / 2) * 60)
	if rpm4 > 1800: return
	if pwms > 95:
		rpm14 = "%.f" % (rpm4,)
	else:
		rpm14 ="%.f" % (np.average(rpm4,))
		time.sleep(0.01)
	t_4 = time.time()
#rpm_mid = str (rpm_mid)
#rpm_mid = "%.f" %((rpm1 + rpm12 + rpm13 + rpm14) / 4)
GPIO.add_event_detect(signal_4, GPIO.FALLING, puls_4)
#			HAUPTSCHLEIFE												
L = ""
lcd.lcd_clear()
time.sleep(2)
GPIO.output(10, GPIO.HIGH)
#try:
while True:
#	try:
#Drehzahl in lesbares Format übersetzen / mitteln (glätten)
		r = "%.1f" % (TemperaturAuswertung(),) #Variable für Temperatur DS18B20
		flankenzeit = np.mean(time.time() - t)
		time.sleep(0.001)
		frequenz = np.mean(1 / flankenzeit)
		rpm = np.average((frequenz / 2) * 60)			
		flankenzeit_2 = np.mean(time.time() - t_2)
		time.sleep(0.001)
		frequenz_2 = np.mean(1 / flankenzeit_2)
		t = time.time()
		rpm11 = np.average((frequenz_2 / 2) * 60)
		t_2 = time.time()
		flankenzeit_3 = np.mean(time.time() - t_3)
		time.sleep(0.001)
		frequenz_3 = np.mean(1 / flankenzeit_3)
		rpm3 = np.average((frequenz_3 / 2) * 60)
		t_3 = time.time()			
		flankenzeit_4 = np.mean(time.time() - t_4)
		time.sleep(0.001)
		frequenz_4 = np.mean(1 / flankenzeit_4)
		rpm4 = np.average((frequenz_4 / 2) * 60) 
		t_4 = time.time() 			
		if pwms > 95:
			rpm1 = "%.f" % (rpm,)
		else:
			rpm1 = "%.f" % (rpm,)
			time.sleep(0.01)
		if pwms > 95:
			rpm12 = "%.f" % (rpm11,)
		else:
			rpm12 = "%.f" % (np.average(rpm11,))
			time.sleep(0.01)
		if pwms > 95:
			rpm13 = "%.f" % (rpm3,)
		else:
			rpm13 = "%.f" % (np.average(rpm3,))
			time.sleep(0.01)
		if pwms > 95:
			rpm14 = "%.f" % (rpm4,)
		else:
			rpm14 ="%.f" % (np.average(rpm4,))
			time.sleep(0.01) 
			lcd.lcd_display_string("Luefter aus.         ", 2)
#Lüfteransteuerung mit Temperaturschwellen und Displayausgabe
		if TemperaturAuswertung() > 30 and TemperaturAuswertung() <= 34:
			r = "%.1f" % (TemperaturAuswertung(),)
			pwms = 25
			lcd.cursor_pos = (0, 0)
			lcd.lcd_display_string("Temp: " + r + " Grd.", 1)
			lcd.cursor_pos = (1, 0)
			lcd.lcd_display_string("PWM:" + str (pwms) + "-" + str (rpm1) + "U/MIN", 2)
		elif TemperaturAuswertung() > 34 and TemperaturAuswertung() <= 38:
			r = "%.1f" % (TemperaturAuswertung(),)
			pwms = 50
			lcd.cursor_pos = (0, 0)
			lcd.lcd_display_string("Temp: " + r + " Grd.", 1)
			lcd.cursor_pos = (1, 0)
			lcd.lcd_display_string("PWM:" + str (pwms) + "-" + str (rpm1) + "U/MIN", 2)
		elif TemperaturAuswertung() > 38:
			r = "%.1f" % (TemperaturAuswertung(),)
			pwms = 100
			lcd.cursor_pos = (0, 0)
			lcd.lcd_display_string("Temp: " + r + " Grd.", 1)
			lcd.cursor_pos = (1, 0)
			lcd.lcd_display_string("Luefter-DRZ Max.        ", 2)
			r = "%.1f" % (TemperaturAuswertung(),)
		else:
			pwms = 0
			lcd.cursor_pos = (0, 0)
			lcd.lcd_display_string("Temp: " + r + " Grd.", 1)
			time.sleep(4)
			lcd.cursor_pos = (1, 0)
			lcd.lcd_display_string("Luefter aus.         ", 2)
		luefter_pwm.start(0)
		Lueftertastverhaeltnis = pwms
		luefter_pwm.ChangeDutyCycle(Lueftertastverhaeltnis)
		time.sleep(2)
		print r + "   " +str(t)
#Drehzahlüberwachung der 4 Gehäuselüfter mit Displayausgabe und LED -Ansteuerung
		if pwms >= 10 and rpm < 100 :
			L = 1
			GPIO.output(led_g, GPIO.LOW)
			GPIO.output(led_r, GPIO.HIGH)
			lcd.lcd_display_string("Luefter Nummer " + str(L), 1)
			lcd.lcd_display_string("!!! Stoerung !!!        ", 2)
		elif pwms >= 10 and rpm11 < 100 :
			L = 2
			GPIO.output(led_g, GPIO.LOW)
			GPIO.output(led_r, GPIO.HIGH)
			lcd.lcd_display_string("Luefter Nummer " + str(L), 1)
			lcd.lcd_display_string("!!! Stoerung !!!        ", 2)
		elif pwms >= 10 and rpm3 < 100 :
			L = 3
			GPIO.output(led_g, GPIO.LOW)
			GPIO.output(led_r, GPIO.HIGH)
			lcd.lcd_display_string("Luefter Nummer " + str(L), 1)
			lcd.lcd_display_string("!!! Stoerung !!!        ", 2)
		elif pwms >= 10 and rpm4 < 100 :
			L = 4
			GPIO.output(led_g, GPIO.LOW)
			GPIO.output(led_r, GPIO.HIGH)
			lcd.lcd_display_string("Luefter Nummer " + str(L), 1)
			lcd.lcd_display_string("!!! Stoerung !!!        ", 2)
		else:
			GPIO.output(led_g, GPIO.HIGH)
			GPIO.output(led_r, GPIO.LOW)
#	except KeyboardInterrupt :
#			print(" Luefteransteuerung manuell beendet! ")
#			luefter_pwm.stop()
#			lcd.lcd_clear()
#			time.sleep(3)
#			lcd.lcd_backlight("off")
#			time.sleep(0.5)
#			GPIO.output(led_g, GPIO.LOW)
#			GPIO.output(led_r, GPIO.LOW)
#			GPIO.cleanup
#sys.exit()

Hier noch die Daten vom Noctua Datasheet:

PWM Ansteurung der Lüfter:

Target frequency: 25kHz, acceptable range 21kHz to 28kHz Maximum voltage for logic low: VIL=0,8V
Absolute maximum current sourced: Imax=5mA (short circuit current) Absolute maximum voltage level: VMax=5,25V (open circuit voltage) Allowed duty-cycle range 0% to 100%

DRZ-Erfassung:

Note that while the tachometer output signal is in Hertz (= per second), fan speeds are usually specifed in RPM (= revolutions per minute).
This means that the signal must be multiplied by 60 in order to convert it to RPM.
However, as the fan puts out two impulses per revolution, the reading must also be divided by 2.

Therefore, the formula for obtaining correct RPM speed is:
fan speed [rpm] = frequency [Hz] × 60 ÷ 2
The calculation for the above example waveform is:
86 [Hz] ÷ 2 × 60 = 2580 [rpm]

Operation below 20% PWM duty-cycle is not ofcially supported in the Intel specifcation (undefned behaviour).
However, most Noctua PWM fans can be operated at below 20% and will stop at 0% duty-cycle.
Only the following models keep running at their specifed minimum speed when the input is below 20%: NF-A20 PWM, NF-S12B redux 1200 PWM and NF-B9 redux 1600 PWM


Vielen, vielen Dank schon Mal für Eure Zeit und Mühe Leute !!!!

Gruß Frank.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Interceptor: Das auslesen des Sensors funktioniert halt nicht immer und dann kann es vorkommen, das die Gerätedatei keine Zeilen liefert. Und dann kann man natürlich nicht auf einen nichtvorhandenen Listeneintrag zugreifen und bekommt die Ausnahme die Du da siehst.

`TemperaturAuswertung()` und damit auch `TemperaturMessung()` werden auch viel zu oft viel zu schnell hintereinander aufgerufen. Wenn man eine gemessene Temparatur gegen eine Ober- und Untergrenze testen will, dann misst man die Temperatur einmal und vergleicht diese *eine* Temperatur dann gegen die Grenzwerte, und man misst nicht und vergleicht mit einer Grenze um *sofort* noch mal zu messen und gegen die andere Grenze zu testen.

Du solltest so schnell es geht von Python 2 weg. Das ist tot.

Vor den Importen sollte kein Code stehen der einen Namen definiert.

Die Importe könnten mal aufgeräumt werden. `datetime`, `sys`, `termios`, und `tty` werden importiert, aber nicht verwendet.

`time` wird importiert und dann wird aus `time` noch mal `sleep()` importiert und im Programm, sogar in der gleichen, kurzen Funktion wird die Funktion dann einmal als `time.sleep()` und kurz danach als `sleep()` aufgerufen.

Was denkst Du was der Sinn von `np.mean()` und `np.average()` ist, wenn man die jeweils auf *einen* Wert anwendet? Das Modul kann wohl auch raus, oder man sollte das sinnvoll nutzen.

``as`` beim Importieren ist zum umbenennen da, `GPIO` wird aber gar nicht umbenannt.

Nach den Importen kommen üblicherweise die Definition von Konstanten, gefolgt von den Definitionen von Klassen und Funktionen, und am Schluss dann das Hauptprogramm — ebenfalls in einer Funktion, die tradionell `main()` heisst. Das Hauptprogramm und vor allem auch Variablen haben auf Modulebene nichts zu suchen. Damit dann am besten auch gleich die Existenz von ``global`` vergessen. Alles was eine Funktion ausser Konstanten benötigt bekommt sie als Argument(e) und Ergebnisse werden als Rückgabewerte an den Aufrufer zurückgegegen. Falls man sich Zustand über Aufrufe hinweg merken muss, verwendet man dafür objektorientierte Programmierung.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Die Kommentare über den beiden Temperatur-Funktionen wären besser Docstrings.

Wenn man Namen von Variablen oder Konstanten mit einem kurzen Kommentar erklären muss, dann ist oft der Kommentar der bessere Name. Statt `SIGNAL_*` mit ``# Lüfter *`` zu kommentieren, hätte man die Konstante auch gleich `LUEFTER` nennen können, und sich den Kommentar sparen.

Namen sollten keine kryptischen Abkürzungen enthalten, oder gar nur aus ihnen bestehen, und man nummeriert auch keine Namen. Dann will man entweder bessere Namen verwenden, oder gar keine einzelnen Namen/Werte sondern eine Datenstruktur. Oft eine Liste.

Warnungen sollte man nicht unterdrücken, sondern die Ursache beseitigen. Es muss sichergestellt werden, dass `GPIO.cleanup()` am Ende des Programmablaufs aufgerufen wird, egal wie das Programm endet. Dazu eignet sich ``try``/``finally``. Falls das Programm durch Strg+C abbrechbar sein soll, kann man das ``except`` dafür auch gleich mit in das Konstrukt aufnehmen.

Es wird zeimal im Programm ``continue`` verwendet und beide Male ist das nicht notwendig. Diese Anweisung sollte man sowieso sehr sparsam einsetzen, denn das ist ein unbedingter Sprung den man nicht am der Struktur des Quelltextes ablesen kann.

Pfadteile setzt man nicht mit ``+`` zusammen. Dafür gibt es das `pathlib`-Modul.

Die Temperaturmessung auf zwei Funktionen zu verteilen erscheint mir sehr/zu aufwändig. Die ”Auswertung” kann man leicht so umschreiben, das die Datei nur an einer Stelle im Code ausgelesen werden muss, und dann lohnt sich dafür keine eigene Funktion.

Falls "t=" nicht in der zweiten Zeile gefunden werden kann, gibt die Messfunktion implizit `None` zurück. Das sollte nicht implizit passieren, weil der Leser sonst nicht weiss ob das Absicht oder ein Programmierfehler ist. Weisst Du es als Autor denn? Der Code der mit dem Ergebnis weiterarbeitet kann damit ja nicht wirklich umgehen.

Dateien sollte man wo möglich mit der ``with``-Anweisung verwenden, um das Schliessen in jedem Fall sicherzustellen.

Bei Textdateien sollte man immer eine Kodierung angeben. Wobei man sich das dekodieren in diesem Fall auch sparen kann und einfach mit den Bytes arbeiten kann.

Statt ``text[-3:] != "YES"`` ist ``not text.endswith("YES")`` IMHO lesbarer.

Die `start()`-Methode von `GPIO.PWM`-Objekten sollte/darf nur einmal aufgerufen werden.

Die `puls()`-Funktion hat einen schlechten Namen, weil der nicht die Tätigkeit beschreibt, die durchgeführt wird. Gilt auch für die nummerierten Kopien der Funktion. Da sich die Funktion Zustand über Aufrufe hinweg merken muss, braucht man hier wie schon gesagt eine eigene Klasse.

Irgendwie ist das auch total komisch und eventuell auch falsch gelöst, denn sowohl bei jedem Puls wird die Startzeit der Messung ermittelt, als auch im Hauptprogramm. Die würde man einmal am Anfang setzen und dann jedes mal wenn man die PWM errechnet, aus den in der Zeit gezählten Pulsen. Die werden im Momant ja gar nicht gezählt, sondern bei jedem Puls wird PWM aufgrund der Zeit zum letzten Puls neu berechnet. Und ggf. in eine Zeichenkette umgewandelt. Von diesen Umwandlungen wird aber nur ab und zu mal eine tatsächlich vom Hauptprogramm ausgegeben. Beziehungsweise da auch noch mal berechnet. Dieser Code steht in vier Funktionen und noch mal viermal im Hauptprogramm. Das ist wirr.

Man kopiert aber auch keinen Code mehrfach. Dafür gibt es Schleifen und/oder Funktionen. Beziehungsweise Klassen, wenn die Funktion sich Zustand merken muss.

Beim Kopieren und anpassen ist anscheinend auch Murks passiert, denn `puls_2` und auch der kopierte Code für den zweiten Lüfter im Hauptprogramm benutzt leicht andere Variablen als das Muster in den anderen drei Kopien. Genau wegen solcher Fehler macht man dieses kopieren und leicht anpassen nicht.

Man könnte den Eindruck bekommen Du würdest denken man müsste/sollte Namen irgendwo am Anfang alle mal an einen Wert binden, auch wenn der dann überhaupt gar nicht verwendet wird. Noch schlimmer: `r` wird an eine 0 gebunden, im restlichen Code dann aber immer für Zeichenketten verwendet. Und bei `L` ist es genau umgekehrt. Das ist verwirrend.

Im Hauptprogramm gibt es viermal hintereinander ``if``/``else`` die ``pwms > 95`` als Bedingung haben. Das wäre besser *eines* mit den Inhalten der Zweige jeweils im ``if`` beziehungsweise ``else``. Wenn die Inhalte nicht so sinnlos wären, weil sich das Ergebnis von einem Wert und dem Mittelwert *eines* Wertes nicht voneinander unterscheiden.

Danach folgt dann ein ``if``/``elif``/``else`` mit vier Zweigen wo in jedem fast der gleiche Code steht. Es steht dort insgesamt 9 mal Code um die Temperatur zu messen. Statt das *einmal* vor dem Konstrukt zu machen. Oh warte, das wird *auch* davor schon mal gemacht. Stehen bloss ein Haufen Zeilen dazwischen die ich einfach löschen konnte, weil die Unsinn machten.

Die Cursorposition vor der Ausgabe von ganzen Zeilen auf dem LCD zu setzen scheint unnötig, denn bei der Lüfterstörungsanzeige wird das nicht gemacht. Die Zeilennummer wird ja auch bei der Ausgabefunktion schon übergeben.

Auch bei der Drehzahlüberwachung gibt es wieder viel kopierten Code. Alle Bedingungen fangen auch mit der gleichen Teilbedingung an, die man hätte herausziehen können. Und überall wo die erste Teilbedingung erfüllt ist, wird mit den LEDs das Gegenteil vom ``else``-Zweig gemacht, auch da hätte man den Teil in den ``if``/``elif``-Zweigen mit der ersten Teilbedingung zusammen herausziehen können.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time
from pathlib import Path
from threading import Lock

import lcddriver
from RPi import GPIO

LUEFTER_PULS_PINS = [13, 15, 19, 21]
LUEFTER_PWM_PIN = 11
NORMALBETRIEB_LED_PIN = 10
LUEFTERSTOERUNG_LED_PIN = 12


def messe_temperatur(device_file_path):
    """
    Die Temperaturauswertung: Beim Raspberry Pi werden erkannte one-Wire Slaves
    im Ordner /sys/bus/w1/devices/ einem eigenen Unterordner zugeordnet. In
    diesem Ordner befindet sich die Datei w1-slave in diesem werden die
    Daten, welche per One-Wire Bus gesendet wurden gespeichert. In dieser
    Funktion werden diese Daten analysiert, die Temperatur herausgelesen und
    ausgegeben.
    """
    while True:
        with open(device_file_path, "rb") as file:
            lines = list(file)

        if len(lines) >= 2 and lines[0].strip().endswith("YES"):
            _, _, number_bytes = lines[1].partition(b"t=")
            try:
                return int(number_bytes) / 1000
            except ValueError:
                pass  # Text can not be interpretet as number.

        time.sleep(0.2)


class PulsZaehler:
    def __init__(self, pin):
        self.lock = Lock()
        self.timestamp = time.monotonic()
        self.anzahl = 0
        GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(pin, GPIO.FALLING, self.zaehle_puls)

    @property
    def rpm(self):
        with self.lock:
            anzahl = self.anzahl
            jetzt = time.monotonic()
            zeit = jetzt - self.timestamp
            self.timestamp = jetzt
            self.anzahl = 0

        return anzahl / zeit * 60

    def zaehle_puls(self, _channel):
        with self.lock:
            self.anzahl += 1


def main():
    try:
        lcd = lcddriver.lcd()

        GPIO.setmode(GPIO.BOARD)
        #
        # TODO Magische Zahl 7 durch eine Konstante ersetzen die dem Leser beim
        #   Verständnis weiterhilft.
        #
        GPIO.setup(
            [LUEFTERSTOERUNG_LED_PIN, NORMALBETRIEB_LED_PIN, LUEFTER_PWM_PIN],
            GPIO.OUT,
            initial=GPIO.LOW,
        )
        #
        # D18B20 Initialisierung
        #
        # Nach Aktivierung des Pull-UP Widerstandes wird gewartet, bis die
        # Kommunikation mit dem DS18B20 Sensor aufgebaut ist.
        #
        GPIO.setup(7, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        base_path = Path("/sys/bus/w1/devices/")
        while True:
            device_path = next(base_path.glob("28*"), None)
            if device_path:
                break

            time.sleep(0.5)

        device_file_path = device_path / "w1_slave"
        #
        # Zur Initialisierung, wird der Sensor einmal "blind" ausgelesen:
        #
        messe_temperatur(device_file_path)

        luefter_pwm = GPIO.PWM(LUEFTER_PWM_PIN, 25_000)
        luefter_pwm.start(0)
        #
        # Definition der Eingänge für die Drehzahlerfassung der 4 Gehäuselüfter.
        #
        pulse_counters = [PulsZaehler(pin) for pin in LUEFTER_PULS_PINS]
        lcd.lcd_clear()
        time.sleep(2)
        GPIO.output(NORMALBETRIEB_LED_PIN, GPIO.HIGH)

        while True:
            temperatur = messe_temperatur(device_file_path)
            rpms = [pulse_counter.rpm for pulse_counter in pulse_counters]

            if temperatur > 38:
                pwms = 100
                text = "Luefter-DRZ Max.        "
            elif temperatur > 34:
                pwms = 50
                text = f"PWM: {pwms} - {rpms[0]} U/Min"
            elif temperatur > 30:
                pwms = 25
                text = f"PWM: {pwms} - {rpms[0]} U/Min"
            else:
                pwms = 0
                text = "Luefter aus.         "

            lcd.lcd_display_string(f"Temp: {temperatur:.1f} Grd.", 1)
            lcd.lcd_display_string(text, 2)

            luefter_pwm.ChangeDutyCycle(pwms)

            luefternummer = None
            for luefternummer, rpm in enumerate(rpms, 1):
                if pwms >= 10 and rpm < 100:
                    break

            if luefternummer is not None:
                lcd.lcd_display_string(f"Luefter Nummer {luefternummer}", 1)
                lcd.lcd_display_string("!!! Stoerung !!!        ", 2)

            GPIO.output(NORMALBETRIEB_LED_PIN, luefternummer is None)
            GPIO.output(LUEFTERSTOERUNG_LED_PIN, luefternummer is not None)

            time.sleep(2)

    except KeyboardInterrupt:
        print(" Luefteransteuerung manuell beendet! ")
        luefter_pwm.stop()
        lcd.lcd_clear()
        time.sleep(3)
        lcd.lcd_backlight("off")
        time.sleep(0.5)
        GPIO.output([NORMALBETRIEB_LED_PIN, LUEFTERSTOERUNG_LED_PIN], GPIO.LOW)
    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Interceptor
User
Beiträge: 3
Registriert: Donnerstag 23. September 2021, 07:13

Hallo blackjack,

schon mal vielen, vielen Dank für Deine Antwort.
Da kann ich leider nicht mithalten...
Du verwendest Code und Syntax, die ich gar nicht kenne, geschweige denn verstehe...
Was ist eine "magic Zahl" ?!?..krass...
Ich hab mir aber Deine Anleitung und das Script rauskopiert und versuche es das nächste Mal besser zum machen ! ;)

Nun zu Deinem Programm:

Hier kommt momentan noch folgender Fehler :

Code: Alles auswählen

SyntaxError: Non-ASCII character '\xc3' in file rm_forum_Script.py on line 69, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
Bitte um Antwort.

Dank Dir schon mal ! :)
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Du benutzt noch Python2. Nimm statt dessen Python3.
Interceptor
User
Beiträge: 3
Registriert: Donnerstag 23. September 2021, 07:13

Hallo Blackjack,

ich konnte 2 Fehler mittlerweile beheben:
den

Code: Alles auswählen

SyntaxError: Non-ASCII character '\xc3'
sowie:

Code: Alles auswählen

File "rm_forum_Script.py", line 98
    luefter_pwm = GPIO.PWM(LUEFTER_PWM_PIN, 25_000)
Hier komme ich aber leider nicht mehr weiter, weil ich die Synax nicht ralle ;) :

Code: Alles auswählen

  File "rm_forum_Script.py", line 117
    text = f"PWM: {pwms} - {rpms[0]} U/Min"
                                          ^
SyntaxError: invalid syntax

Bitte um Hilfe.

Danke schon mal !
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Du solltest zumindest Python 3.6 verwenden.
Antworten