Ich hab mich mit dem Thema noch etwas auseinander gesetzt und hab das mangels Hardware ohne Kamera und ohne Pushbullet nachgestellt. Auch, hab ich jetzt nicht am Pi geschrieben und habe den Pir per "mock" eingebaut.
Zuerst war das Problem das der Header zur Mail mehrfach angelegt wurde und dadurch folgende Meldung kam:
ValueError: There may be at most 1 Subject headers in a message
Dann hab ich den Header in der main() 1x erstellt und diesen immer wieder, bei einer neuen Mail, aufgerufen, was dann lief. Allerdings war das Bild nach mehrfachen senden auch mehrfach im Anhang. Also 1. mal Mail senden - 1 Bild im Anhang, 2. mal senden - 2x das selbe Bilde im Anhang, 3. mal senden - 3x das selbe Bild im Anhang usw..
In der Doku steht das man den Inhalt des Headers löschen kann. Da hätte ich mal eher reinschauen sollen. 
python.org - email.message.EmailMessage.clear
Wie dem auch sei, danach liefs so wie mans braucht.
Das senden der Mail hab ich in einer Schleife einegbaut und auch was da den Aufruf zum senden der Mail war. Also ob der Pir oder schedule.
Die Aufrufe musste ich Zeitlich pausieren da sonst wieder obige Fehlermeldung kam. Keine Ahnung warum und ich hatte jetzt auch keine Lust mehr danach zu suchen. Vllt kann mir das jemand beantworten, der tiefer in der Materie steckt.
Hier das Testscript + Yaml.config:
pip install loguru
pip install pyyaml
server:
host: "mail-host"
port: "port"
user: "deine_mail@bla.de"
password: "password"
message:
from: "deine_mail@bla.de"
to: ""eine_andere_mail@bla.de"
subject: "Wir senden eine Mail mit Python"
path:
image: "/pfad/zum/image/dein_image.jpg"
Display More
import schedule
import threading
import smtplib
import imghdr
import yaml
from gpiozero.pins.mock import MockFactory
from gpiozero import Device, MotionSensor
from functools import partial
from time import sleep
from loguru import logger
from email.message import EmailMessage
from pathlib import Path
from datetime import datetime
CONFIG_PATH = Path("/dein/pfad/zur/config/config.yml")
def open_config():
with open(CONFIG_PATH, 'r') as file:
return yaml.safe_load(file)
def create_message(msg):
config = open_config()
msg['Subject'] = config["message"]["subject"]
msg['From'] = config["message"]["from"]
msg['To'] = config["message"]["to"]
with open(config["path"]["image"], 'rb') as fp:
img_data = fp.read()
msg.add_attachment(img_data, maintype='image',
subtype=imghdr.what(None, img_data))
return msg
def send_my_mail(msg):
config = open_config()
with smtplib.SMTP_SSL(config["server"]["host"], config["server"]["port"]) as s:
s.login(config["server"]["user"], config["server"]["password"])
s.send_message(create_message(msg))
msg.clear()
now = datetime.now()
print(f"Mail gesendet am/um: {now.strftime('%d/%m/%Y %H:%M')} Uhr")
def task(msg, ausloeser):
print(f"Task Ausgelöst durch: {ausloeser}")
print("current thread", threading.current_thread())
send_my_mail(msg)
print("Task Ende\n")
@logger.catch()
def main():
print("Start\n")
Device.pin_factory = MockFactory()
pir = MotionSensor(7)
msg = EmailMessage()
pir.when_motion = partial(task, msg, "pir")
schedule.every(3).seconds.do(task, msg, "schedule")
try:
while True:
#Pausenzeiten mussten mindestens 2 sec. betragen, da sonst Fehlermeldung:
#ValueError: There may be at most 1 Subject headers in a message
sleep(1)
pir.pin.drive_high()
sleep(2)
pir.pin.drive_low()
sleep(2)
schedule.run_pending()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()
Display More
Beispiel Ausgabe:
Start
Task Ausgelöst durch: pir
current thread <GPIOQueue(Thread-1 (fill), started daemon 124209172051520)>
Mail gesendet am/um: 02/12/2023 16:51 Uhr
Task Ende
Task Ausgelöst durch: schedule
current thread <_MainThread(MainThread, started 124209208483840)>
Mail gesendet am/um: 02/12/2023 16:51 Uhr
Task Ende
Task Ausgelöst durch: pir
current thread <GPIOQueue(Thread-1 (fill), started daemon 124209172051520)>
Mail gesendet am/um: 02/12/2023 16:51 Uhr
Task Ende
Task Ausgelöst durch: schedule
current thread <_MainThread(MainThread, started 124209208483840)>
Mail gesendet am/um: 02/12/2023 16:51 Uhr
Task Ende
Display More
luemar
Wäre schön, wenn du das folgende Script nochmal testen könntest. Das liegt nicht an schedule oder der paus(). Das liegt eher daran wie das mit der Mail bzw. Camera aufgebaut ist.
Wenn dein jetziges Script schon als Service läuft, stop den Service bevor du das folgende Script testest. Wenn du mit strg-C das Script nicht anhalten kannst warte noch ein wenig, denn, wenn das Script gerade ein Bild aufnimmt oder die Mail sendet kann es noch kurz dauern bis das Script beendet wird oder du spamst die Konsole mit strg-C voll.
Config:
Den Pfad zur Config-Datei musst du im Script angeben. Einfach eine Text-Datei erstellen den Inhalt rein kopieren und in config.yml umbennen.
server:
host: "mail-host"
port: "port"
user: "deine_mail@bla.de"
password: "password"
message:
from: "deine_mail@bla.de"
to: ""eine_andere_mail@bla.de"
subject: "von RPi Bonda, Foto von RPi Seemoeve"
path:
image: "/pfad/zum/image/dein_image.jpg"
pushbullet:
api_key: "mein API-Key"
telefonnummer: "meine Mobile Nummer"
gpio_pin:
relais: 13
led: 5
pir: 6
trigger_time: "12:00"
Display More
Script:
Teste das ganze am besten mit Thonny und nicht in der Console und übernimm das ganze Script und nicht nur Teile.
In Zeile 27 muss der Pfad zur config Datei angepasst werden.
#!/usr/bin/python3
import schedule
import smtplib
import imghdr
import yaml
import smtplib
import schedule
import imghdr
import logging
from gpiozero import DigitalOutputDevice, LED, MotionSensor
from picamera import PiCamera
from pushbullet import Pushbullet
from time import sleep
from functools import partial
from pathlib import Path
from email.message import EmailMessage
from datetime import datetime
logging.basicConfig(filename='/home/pi/my_log.log',
filemode='w',
level=logging.INFO,
format='%(message)s:%(asctime)s')
CONFIG_PATH = Path("/pfad/zur/config.yml")
with open(CONFIG_PATH, 'r') as file:
config = yaml.safe_load(file)
def get_picture():
camera = PiCamera()
with camera as cam:
cam.rotation = 0
cam.start_preview()
sleep(1)
cam.capture(Path(config["path"]["image"]),
resize=(640, 480))
def create_message(msg):
msg['From'] = config["message"]["from"]
msg['To'] = config["message"]["to"]
msg['Subject'] = config["message"]["subject"]
with open(config["path"]["image"], 'rb') as fp:
img_data = fp.read()
msg.add_attachment(img_data, maintype='image',
subtype=imghdr.what(None, img_data))
return msg
def send_my_mail(msg):
with smtplib.SMTP_SSL(config["server"]["host"], config["server"]["port"]) as s:
s.login(config["server"]["user"], config["server"]["password"])
s.send_message(create_message(msg))
msg.clear()
now = datetime.now()
print(f"Mail gesendet am/um: {now.strftime('%d/%m/%Y %H:%M')} Uhr")
def send_sms(pushbullet):
try:
pushbullet.push_sms(pushbullet.devices[0],
config["pushbullet"]["telefonnummer"],
"PIR home Seemoeve aktiviert")
except PushbulletError as e:
print(f"SMS error: {e})")
def task(trigger, camera_light,
msg, pushbullet):
logging.info(f"Task gestartet durch: {trigger}")
camera_light.on()
sleep(1)
get_picture()
camera_light.off()
send_my_mail(msg)
send_sms(pushbullet)
logging.info("Task Ende")
def main():
logging.info("Motion gestartet")
status_led = LED(config["gpio_pin"]["led"])
status_led.on()
camera_light = DigitalOutputDevice(config["gpio_pin"]["relais"])
pir = MotionSensor(config["gpio_pin"]["pir"])
msg = EmailMessage()
pushbullet = Pushbullet(config["pushbullet"]["api_key"])
pir.when_motion = partial(task, "Pir",
camera_light, msg, pushbullet)
schedule.every().day.at(config["trigger_time"]).do(task, "schedule", camera_light,
msg, pushbullet)
while True:
schedule.run_pending()
sleep(1)
if __name__ == "__main__":
main()
Display More
Ungetestet. Ich hoffe ich hab nichts vergessen.