Skriptaufruf passiert zwei Mal | RPi 3 | GPIO.add_event_detect

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
ds91
User
Beiträge: 5
Registriert: Dienstag 5. Mai 2020, 12:26

Hallo zusammen!

Ich bin Python technisch noch relativ am Anfang bzw. google mich durch sämtliche Probleme, die ich so habe. Bisher konnte ich alles recht gut damit lösen!
Kurz zu meinem Projekt: ich habe mit einem RPi 3 ein System gebastelt, welches SMS empfängt, die SMS nach einem bestimmten Codewort durchsucht und wenn jenes gefunden wird, den SMS Inhalt auf einem 16x4 LCD anzeigt und dazu einen Audiovisuellen Alarm mit Buzzer und LEDs ausgibt. Das funktioniert eigentlich alles gut.
Des weiteren habe ich zwei physische Taster an meinem Gerät. Mit dem einen kann ich die letzte empfangene SMS nochmals anzeigen lassen und mit dem anderen kann der Audiovisuelle Alarm abgebrochen werden. Die Software Basis bildet hierbei ein ständig laufender Hintergrundprozess, der beim Booten mitstartet. Dieser Prozess wartet auf die Eingabe vom Taster mit der letzten SMS Anzeige mittels dem Befehl

Code: Alles auswählen

GPIO.add_event_detect(gpio_button, GPIO.FALLING, callback=sms_recall, bouncetime = 500)

Die Funktion sms_recall sieht so aus:

Code: Alles auswählen

def sms_recall(gpio_button):	
	os.system("sudo python /home/pi/scripts/call_last_sms.py")
	return
Das Problem hierbei ist jedoch, dass das dort aufgerufene Skript call_last_sms.py immer zweimal (nicht mehr und nicht weniger) aufgerufen wird! In diesem Skript sind keinerlei Schleifen oder ähnliches eingebaut, ein einfacher Code, der einmal durchlaufen werden soll. Das Eigenartige: kommentiere ich den Befehl os.system("... aus und ersetze ihn z.B. mit einem einfachen print, dann wird die Funktion nur einmal ausgeführt und die Ausgabe wird auch wirklich nur so oft ausgegeben, wie ich den Taster drücke. Der Taster ist auch Hardwaretechnisch zusätzlich zum bouncetime = 500 entprellt. Und das bestätigt sich ja auch dadurch, dass eine print Ausgabe nur einmal je Tastendruck erscheint. Ich habe auch schon versucht, mit einer globalen Variable und einer if-Abfrage das mehrfache ausführen zu verhindern. Das ginge grundsätzlich, allerdings möchte ich ja, dass bei einem erneuten Tastendruck die SMS wieder angezeigt wird. Wenn ich das mit einer Abfrage blockiere, geht das natürlich nicht.

Die Frage lautet also: wieso wird der Befehl os.system("... zwei mal ausgeführt?!
Ich weiß, ich gehe mit diesem Befehl über das OS und bleibe nicht in Python, aber anders habe ich es noch nicht geschafft, das Skript aufzurufen. Ich wollte auch schon den kompletten Inhalt des Skripts call_last_sms.py in die Funktion packen. Leider hakt es dort gewaltig in Bezug auf den Import von datetime. Kann mir jemand sagen, was hier schieflauft?

Der Code ist bestimmt nicht der schönste und einiges könnte man vermutlich noch effektiver gestalten, aber mehr Wissen hab ich im Moment nicht und es funktioniert. Hier also nochmal der gesamte der Code:

Hintergrundprozess "lcd_startup_init.py":

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Script 4
# 
# Description: 
# + Show time and alarm status at startup for 5 minutes after RPi startup
# + Monitor GPIO Input for calling last SEG SMS

from __future__ import print_function, division, absolute_import, unicode_literals
import os
import glob
import sys
from time import sleep, strftime
from time import *
import time
import datetime

from RPLCD.i2c import CharLCD
import RPi.GPIO as GPIO
import codecs
from itertools import islice


lcd = CharLCD(i2c_expander="PCF8574", address=0x27,port=1,cols=20,rows=4,
              dotsize=8,charmap="A02",auto_linebreaks=True,backlight_enabled=False)
              
gpio_button = 26
GPIO.setmode(GPIO.BCM) # BCM = use logic numbering of pins (not physical)
GPIO.setup(gpio_button, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
 
display_minutes = 0
displaytime = 1 
line1 = 1
line2 = 2

lcd.backlight_enabled = True

def sms_recall(gpio_button):	
	os.system("sudo python /home/pi/scripts/call_last_sms.py")
	return

	
		
# interrupt this code immediately (anytime) when button is pressed:
GPIO.add_event_detect(gpio_button, GPIO.FALLING, callback=sms_recall, bouncetime = 500)
sleep(0.5) # sleep to give CPU time to react on event



while True:
	lcd.clear()
	lcd.cursor_pos = (line1,0)
	lcd.write_string("     Kein Alarm")
	lcd.cursor_pos = (line2,0)
	lcd.write_string(datetime.datetime.now().strftime("       %H:%M"))
	sleep(60)
	display_minutes += 1
	
	if display_minutes == displaytime:	# turn backlight off after set number of minutes
		lcd.backlight_enabled = False
		lcd.clear()
		
	# swap lines to prevent possible LCD burn-in effect:
	if display_minutes % 2 != 0:
		line1 = 2
		line2 = 1
	else:
		line1 = 1
		line2 = 2
Aufruf der letzten SMS "call_last_sms.py":

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

############################### 
# Script 1
#
# Description: 
# Read last incoming SMS again and show it on the LCD 
# multiple times
#
# Created on: 09.03.2020
# Changed: 05.05.2020
#
############################### 

from __future__ import print_function, division, absolute_import, unicode_literals
from time import *
from RPLCD.i2c import CharLCD
from itertools import islice

import os
import glob
import sys
import time
import datetime
import textwrap
import codecs
import RPi.GPIO as GPIO
from datetime import datetime


############################### 
# Define Counters and Variables
linecount = 0

############################### 



lcd = CharLCD(i2c_expander="PCF8574", address=0x27,port=1,cols=20,rows=4,
              dotsize=8,charmap="A00",auto_linebreaks=True,backlight_enabled=True)


# open files with codecs. method to show german umlaute like ü ä ö
with codecs.open("/home/pi/processed_sms/processed_sms.txt","r","utf-8") as processed_file_read:
  
  for line in processed_file_read:
    linecount += 1
  
  # show last SEG SMS on LCD:
  timestamp = os.path.getmtime("/home/pi/processed_sms/processed_sms.txt")
  date_time = datetime.fromtimestamp(timestamp)
  datum = date_time.strftime("%d.%m.%Y")
  zeit = date_time.strftime("%H:%M")
  lcd.cursor_pos = (0,0)
  lcd.write_string("  Letzte SEG SMS:")
  lcd.cursor_pos = (1,0)
  lcd.write_string("--------------------")
  lcd.cursor_pos = (2,0)
  lcd.write_string("Datum:")
  lcd.cursor_pos = (2,7)
  lcd.write_string(datum)
  lcd.cursor_pos = (3,0)
  lcd.write_string("Uhrzeit:")
  lcd.cursor_pos = (3,9)
  lcd.write_string(zeit)
  sleep(5)
  
  times_to_display = 1
  display_count = 1
  
  # show SMS text x times on LCD
  #while display_count <= times_to_display:
    
  # set current file position in text file with seek() in order to "jump around"
  processed_file_read.seek(0)
  
  # read lines 0 to 4 of file
  new_data_0_4 = processed_file_read.readlines()[0:4]    
  lcd.clear()
  lcd.write_string("".join(new_data_0_4))
  sleep(3) 
  
  if linecount > 4:
    # set new current file position in text file
    processed_file_read.seek(1)
    
    # read next lines 4 to number of lines
    new_data_5_8 = processed_file_read.readlines()[4:8]    
    lcd.clear()
    lcd.write_string("".join(new_data_5_8))
    sleep(3)
    
  if linecount > 8:
    processed_file_read.seek(2)
    # read lines 4 to number of lines
    new_data_max = processed_file_read.readlines()[8:(linecount)]    
    lcd.clear()
    lcd.write_string("".join(new_data_max))
    sleep(3)
    
lcd.clear()
lcd.cursor_pos = (1,0)
lcd.write_string("  Bitte warten....")
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

In Event-Handlern sollte man nur ganz kurz, ganz einfache Sachen machen, weil die als eigener Thread nebenher laufen.
Typischerweise bestehen meine Event-Handler nur aus einem queue.put, und die Queue frage ich in der Hauptschleife ab.

Dann sind die beiden Python-Programma ja soo ähnlich, beide greifen auf das SELBE LCD zu, dass das zu keinen Problemen führt ...

Packe das was in last_call_sms.py steh in eine Funktion, die Du vom anderen Programm in der Hauptschleife aufrufst.

PS: eingerückt wird in Python immer mit 4 Leerzeichen pro Ebene, nicht mal Tabs und mal 2 Leerzeichen.
ds91
User
Beiträge: 5
Registriert: Dienstag 5. Mai 2020, 12:26

Danke für deine Antwort!
Ich weiß nur nicht genau, wie du das mit dem Aufrufen der Fkt. in der Hauptschleife meinst bzw. wie ich das umsetzen soll. Mit GPIO.add_event_detect(gpio_button, GPIO.FALLING, callback=sms_recall, bouncetime = 500) rufe ich ja die parallel laufenden Thread "sms_recall" auf. Wie mache ich das, dass ich von dort eine "lokale" Funktion aufrufe? Alles, was ich in dieser sms_recall Funktion mache, läuft doch parallel.
Außer dieses GPIO.add_event_detect() habe ich noch keine Möglichkeit gefunden, einen Taster ständig abzufragen. Und diese Fkt. ruft ja immer eine parallel laufende callback Fkt. auf, anders lässt sich das ja nicht einsetzen.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst das Ereignis, dass da etwas gekommen ist, per Queue in die Hautpschleife kommunzieren. Dazu finden sich hier auch diverse Themen, einfach mal ein bisschen mit google site-search arbeiten.

Und du irrst dich, das "alles parallel" laufen wuerde. Da laeuft nicht pro Event-Ueberwachung ein Thread. Da ist genau EIN anderer Thread, und der blockiert so lange, wie du etwas machst.
Antworten