DIY Klingel für Haus programmieren - Robustheit

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da fehlt ne Klammer zu in der Zeile davor.
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

Hallo,

heute gig es im Büro etwas länger, deshalb komme ich erst jetzt zum Testen. Einige Error Messages konnte ich selber finden/Lösen, aber bei diesen beiden finde ich leider nichts. Darf ich euch nochmal um Hilfe bitten?

Code: Alles auswählen

[tcp @ 0x12f2700] Connection to tcp://192.168.100.111:9000 failed: Connection refused
http://192.168.100.111:9000/?action=stream: Connection refused
Aufzeichnung beendet
Traceback (most recent call last):
  File "ring.py", line 89, in <module>
    main()
  File "ring.py", line 84, in main
    record(ring_count)
  File "ring.py", line 71, in record
    send_message(CHAT_IDS, message=message)
  File "ring.py", line 40, in send_message
    params = {"chat_id": id}
UnboundLocalError: local variable 'id' referenced before assignment
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

Hallo ihr wissenden :)

ich kämpfe (tapfer) um das zum Laufen zu bekommen.

Leider komme ich mit dieser Fehlermeldung jetzt so gar nicht weiter. Dahinter habe ich noch den aktuellen code angehängt.

Ich hoffe Ihr habt noch einmal die Lust mir zu helfen.

Danke

Code: Alles auswählen

[tcp @ 0x252d700] Connection to tcp://192.168.100.111:9000 failed: Connection refused
http://192.168.100.111:9000/?action=stream: Connection refused
Aufzeichnung beendet
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
{'ok': False, 'description': 'Unauthorized', 'error_code': 401}
Videoaufzeichnung gestartet
Also nach den Fehlern, startet das Skript neu ...
Warum versucht er mit TCP zu verbinden?

Aktueller Code:

Code: Alles auswählen

#!/usr/bin/env python
#coding: utf8

import time
import os
import subprocess
import datetime
from itertools import count
import requests
import RPi.GPIO as gpio
import glob

DEVELOPERMODE = True

# PIN festlegen an dem das Signal kommt 
OPTOKOPPLER = 18
RELAIS = 11

#Telegram konfiguration
TELEGRAM_URL ="https://api.telegram.org/bot"
BOT_TOKEN = "75377000000000000000000000000000pp2w5lNY18"
KATHLEEN_ID = "7600000007"
HEINZ_ID = "70000000004"
CHAT_IDS = [HEINZ_ID, KATHLEEN_ID]

BASE_PATH = '/home/pi/cam'

VIDEO_START_COMMAND = ['mjpg_streamer',
    '-i', "/usr/local/lib/input_uvc.so -d /dev/video0 -n -r 1024x768 -f 24 -q 80"
    '-o', "/usr/local/lib/output_http.so -n -w /usr/local/www -p 9000",
    '-o', "/usr/local/lib/output_file.so -f /home/pi/cam -d 12000"]
VIDEO_CONVERT_COMMAND = ['ffmpeg',
    '-i', "http://192.168.100.111:9000/?action=stream",
    '-vcodec', 'copy', '-an', '-t', '30', '-bufsize', '2048k',
    '-y']

def send_message(chatroomids, message=None, photo=None):
    """ versendet die Klingelbenachrichtigung """
    files = {}
    global  id
    params = {"chat_id": id}
    if message:
        params["text"] = message
        cmd = "sendMessage"
    if photo:
        files = {"photo": photo}
        cmd = "sendPhoto"
    url = TELEGRAM_URL + BOT_TOKEN + "{}{}/{}".format(TELEGRAM_URL, BOT_TOKEN, cmd)
    for id in chatroomids:
        r = requests.get(url, params=params, files=files)
        result = r.json()
        print(result)

def setup():
    # Pin optokopller als Eingang, relais als Ausgang festlegen
    gpio.setmode(gpio.BOARD)
    gpio.setup(OPTOKOPPLER, gpio.IN, pull_up_down = gpio.PUD_UP)
    gpio.setup(RELAIS, gpio.OUT)
    # Klingelrelais generell anschalten
    gpio.output(RELAIS, gpio.HIGH) 

def record(ring_count):
    print("Videoaufzeichnung gestartet")
    filename = os.path.join(BASE_PATH, "Klingel-{:%Y%m%d_%H%M%S}.mp4".format(datetime.datetime.now()))
    video = subprocess.Popen(VIDEO_START_COMMAND)
    print("Video start")
    convert = subprocess.Popen(VIDEO_CONVERT_COMMAND + [filename])
    print("Aufnahme start")
    time.sleep(31)
#    video.kill()
    video.terminate()
    convert.wait()
    print("Aufzeichnung beendet")
    message = "Es hat gerade das {}. mal geklingelt.".format(ring_count)
    send_message(CHAT_IDS, message=message)
    for filename in sorted(glob.glob(os.path.join(BASE_PATH, '*.jpg'))):
        with open(filename, 'rb') as data:
            photo = data.read()
        send_message(CHAT_IDS, photo=photo)
        time.sleep(1)
        os.unlink(filename)

def main():
    try:
        setup()
        for ring_count in count():
            if DEVELOPERMODE or gpio.wait_for_edge((OPTOKOPPLER), gpio.FALLING):
                record(ring_count)
    finally:
        gpio.cleanup()

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Homer-S: HTTP läuft über TCP.

Ich sehe da ein ``global`` im Code – bitte sofort vergessen das es dieses Schlüsselwort in Python gibt. Das ist falsch. Es funktioniert ja auch gar nicht, denn der Name ist ja auch ``global`` an der Stelle gar nicht definiert. Da wird dann halt aus dem `UnboundLocalError` ein `NameError`.

Nach dem `video.terminate()` fehlt noch ein `video.wait()`. Wenn man den Prozess nicht mehr brutal vom Kernel abschlachten lässt, muss man warten bis er tatsächlich von selbst zum Ende gekommen ist.

Das die Verbindung nicht angenommen wird, kann unter Umständen an dem Problem liegen was ich schon mal angesprochen hatte: Der zweite Prozess kann sich erst zum ersten Verbinden wenn der auch auf dem Port auf Verbindungen wartet. Man müsste warten bis der tatsächlich da ist.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

__blackjack__ hat geschrieben: Freitag 10. Mai 2019, 23:14 @Homer-S: HTTP läuft über TCP.

Ich sehe da ein ``global`` im Code – bitte sofort vergessen das es dieses Schlüsselwort in Python gibt. Das ist falsch. Es funktioniert ja auch gar nicht, denn der Name ist ja auch ``global`` an der Stelle gar nicht definiert. Da wird dann halt aus dem `UnboundLocalError` ein `NameError`.

Nach dem `video.terminate()` fehlt noch ein `video.wait()`. Wenn man den Prozess nicht mehr brutal vom Kernel abschlachten lässt, muss man warten bis er tatsächlich von selbst zum Ende gekommen ist.

Das die Verbindung nicht angenommen wird, kann unter Umständen an dem Problem liegen was ich schon mal angesprochen hatte: Der zweite Prozess kann sich erst zum ersten Verbinden wenn der auch auf dem Port auf Verbindungen wartet. Man müsste warten bis der tatsächlich da ist.
Wenn ich das "global id" wieder raus nehme, bekomme ich diesen Fehler:

UnboundLocalError: local variable 'id' referenced before assignment

Dazu hatte ich nur das mit dem global gefunden ...???


Wegen dem "zweiten Prozess" ... wieso hatte das in meiner gestümperten Version dann aber funktioniert?
Kann es da ein Zugriffs- Rechte-Problem geben? Wenn ja was müsste ich prüfen?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Idee war es, die for-Schleife zu vereinfachen, war dann aber zu einfach.
Du mußt also nur die Funktion verstehen, um sie zu reparieren. Raten ist aber kein guter Weg.

Wegen der zwei Prozesse: Du hast ein Timing-Problem, das mal auftritt und mal nicht, funktioniert hat das also nie wirklich richtig.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Homer-S: Welchen Wert muss denn `id` an der Stelle haben wo es am Ende benutzt wird? Und wo bekommst Du diesen Wert her? Ganz sicher nicht durch ein Schlüsselwort was man mal eben davor schreibt. Der Code ist logisch falsch angeordnet. Das muss man durch nachdenken und korrigieren lösen, nicht durch Googlen und/oder raten.

Das mit den beiden Prozessen hat in Deiner Version genau so funktioniert wie jetzt auch. Es wird ein Prozess gestartet der als Server auf einem Port lauscht und fast gleichzeitig einer der sich mit diesem Port verbindet. Das kann immer ”funktionieren”, oder ”nie”, oder mal ja, mal nein. Es ist letztlich Glückssache ob der erste Prozess zu dem Zeitpunkt wo der zweite sich verbinden will, schon soweit ist, oder eben noch nicht. Eine klassische Wettlaufsituation („race condition“) bei nebenläufiger Programmierung. Ich würde da einfach nach dem starten des ersten Prozesses in einer Schleife eine Weile versuchen mich mit dem Port zu verbinden und erst den zweiten Prozess starten, wenn das funktioniert hat. Oder wenn das innerhalb ein oder zwei Sekunden nicht geklappt hat, das Programm mit einer Fehlermeldung abbrechen. Natürlich nach dem beenden des ersten Prozesses.

Und das ``time.sleep(31)`` ist falsch/überflüssig, weil man *erst* auf den `convert`-Prozess warten muss und dann erst den `video`-Prozess terminieren darf. Ja, theoretisch könnte auch das ”immer” funktionieren, muss es aber nicht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

Ich glaube den 'id' Fehler konnte ich lösen. Ganz sicher bin ich mir nict, aber es erscheint mir logisch:

Code: Alles auswählen

def send_message(chatroomids, message=None, photo=None):
    """ versendet die Klingelbenachrichtigung """
    files = {}
    for id in chatroomids:
        params = {"chat_id": id}
        if message:
            params["text"] = message
            cmd = "sendMessage"
        if photo:
            files = {"photo": photo}
            cmd = "sendPhoto"
        url = TELEGRAM_URL + BOT_TOKEN + "{}{}/{}".format(TELEGRAM_URL, BOT_TOKEN, cmd)
        r = requests.get(url, params=params, files=files)
        result = r.json()
        print(result)
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

Hallo,

ich muss leider schon wieder schreiben. Ich habe mir den Punkt mit den zwei Prozessen zu Herzen genommen.

Gibt es eine Funktion/Lib, mit der man die Webseite sowas wie anpingen kann und dann den anderen Prozess zu starten?

Dann ist mir (langsam erkenne ich auch etwas mehr) aufgefallen, dass die count() funktion nicht definiert ist.

Code: Alles auswählen

def main():
    try:
        setup()
        for ring_count in count():
            if DEVELOPERMODE or gpio.wait_for_edge((OPTOKOPPLER), gpio.FALLING):
                record(ring_count)
wenn ich das richtig verstanden habe, ist das nicht das, was ich will, oder?
Mein Ansinnen ist, dass jedes Klingeln in einer Variable gespeichert wird und in der Message an mich als 1tes mal 2tes mal usw angezeigt wird.

Oder ersetzt diese for Schleife mein "while"?

Danke
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Lies dir doch mal die Dokumentation zu count() durch. Das beantwortet hoffentlich die Frage zur Schleife.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Natürlich ist `count` definiert, wird nämlich aus itertools importiert. Und was es macht, steht in der Dokumentation dazu.

Prüfen, ob der Server schon bereit ist, würde ich ja `lsof` benutzen, oder halt direkt was von psutil.
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

Tut mir Leid, wenn ich hier nochmal nachfragen muss, ich steig nur nicht durch.

Was genau bedeutet diese Zeile?

for ring_count in count():

wenn ich alles, was ich gelesen habe richtig verstehe, ist count() dafür da, Anzahl Zeichen oder sowas zu zählen, bzw eine Zahlenreihe zu generieren.
Hier ist es aber ja, mehr oder weniger, None.

dann würde man sagen wiederhole für jeden ring_count in count() ...
also 0 in none??

Sorry, da bin ich echt vor einer Mauer, gedanklich.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Was verstehst Du an der Dokumentation nicht?
Hast Du schon ausprobiert, was es tut? Das wäre das einfachste.
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

@Homer-S: Wenn du sagst "es aber ja, mehr oder weniger, None", meinst du dann eher 2/3 None? 1/6 None? Und was ist der Rest, wenn es nur mehr oder weniger None ist? So funktioniert Programmieren in der Regel nicht. Du musst nicht raten was das ist, du kannst es einfach ausprobieren. Sind ja nur 3 Zeilen Code itertools zu importieren, den Schleifenkopf zu schreiben und dann in der Schleife ausgeben zu lassen, was ring_count für einen Wert hat.

Wenn der Entwickler es hinterlegt hat, kommt man übrigens mit help(Objekt) an eine Hilfe im interaktiven Interpreter. Nur für den Fall, dass du die Dokumentation suchst.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Gib einfach

Code: Alles auswählen

from itertools import count 
for i in count():
     print(i)
ein und schau was passiert.
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

sparrow hat geschrieben: Mittwoch 15. Mai 2019, 07:11 @Homer-S: Wenn du sagst "es aber ja, mehr oder weniger, None", meinst du dann eher 2/3 None? 1/6 None? Und was ist der Rest, wenn es nur mehr oder weniger None ist? So funktioniert Programmieren in der Regel nicht. Du musst nicht raten was das ist, du kannst es einfach ausprobieren. Sind ja nur 3 Zeilen Code itertools zu importieren, den Schleifenkopf zu schreiben und dann in der Schleife ausgeben zu lassen, was ring_count für einen Wert hat.

Wenn der Entwickler es hinterlegt hat, kommt man übrigens mit help(Objekt) an eine Hilfe im interaktiven Interpreter. Nur für den Fall, dass du die Dokumentation suchst.
Wenn ich mich so vorsichtig ausdrücke, liegt es daran, dass ich hier nicht Behauptungen aufstellen will die sich dann als komplett Falsch raus stellen.

Was das Resultat ist hab ich schon raus gefunden, aber WARUM funktioniert das. Das kapiere ich nicht.

Das was ich aus der Doku lese ist, dass count() etwa zählt, was in den Klammern steht. wie weiß das ding, dass es die ring_count hochzählen soll. :shock:
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Homer-S: Das weiss das Ding gar nicht, das ist ihm auch völlig egal. Das liefert einfach Zahlen. Das die dann dem Namen `ring_count` zugewiesen werden, ist die Aufgabe der ``for``-Schleife. Wenn Du da ``for vorname in ['Peter', 'Paul', 'Mary']:`` schreibst, dann weiss die Liste mit den Namen ja auch nicht das die Namen an `vorname` gebunden werden. Das macht die ``for``-Schleife. Genau so weiss bei ``for i in count():` das Objekt das von `count()` erzeugt wird, nichts von dem `i`.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Homer-S hat geschrieben: Mittwoch 15. Mai 2019, 09:36Das was ich aus der Doku lese ist, dass count() etwa zählt, was in den Klammern steht. wie weiß das ding, dass es die ring_count hochzählen soll. :shock:
Aus welcher Dokumentation hast du das denn gelesen? Kannst du die mal verlinken? Das ist nämlich auf keinen Fall das, was in der Dokumentation der Funktion steht.
Und wenn du in meinen vorherigen Beitrag schaust: Da habe ich dir doch geschrieben, wie man auf eine ganz einfache Weise an Hilfe für die Funktion kommt (falls der Entwickler sie sauber im Code hinterlegt hat, was in diesem Fall so ist.)
Homer-S
User
Beiträge: 30
Registriert: Freitag 1. Dezember 2017, 22:30

__blackjack__ hat geschrieben: Mittwoch 15. Mai 2019, 09:46 @Homer-S: Das weiss das Ding gar nicht, das ist ihm auch völlig egal. Das liefert einfach Zahlen. Das die dann dem Namen `ring_count` zugewiesen werden, ist die Aufgabe der ``for``-Schleife. Wenn Du da ``for vorname in ['Peter', 'Paul', 'Mary']:`` schreibst, dann weiss die Liste mit den Namen ja auch nicht das die Namen an `vorname` gebunden werden. Das macht die ``for``-Schleife. Genau so weiss bei ``for i in count():` das Objekt das von `count()` erzeugt wird, nichts von dem `i`.
Das hat geholfen!

das vorname ein .... ist die Art wie ich es kenne/verstehe. Ich konnte nicht erkennen, dass ein count() einfach bei 0 das zählen beginnt bis zum Sankt Nimmerleinstag ...
Besten Dank

Ich bin gerade in der Arbeit und kann schlecht testen. Nachdem, was ich gestern Abend mit psutil.net_connections() ausgegeben bekommen habe müsste eine einfach Abfrage den laufenden Prozess erfragen können:
Der Ausgabe Port der cam ist 9000

if '9000' in str(psutil.net_connections()):
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Homer-S: nein, die String-Repräsentation ist nicht dafür gedacht, darin wild nach Zahlen zu suchen.
Schau Dir doch mal genau an, was diese Funktion als Rückgabewert hat, wie Du einen einzelnen Eintrag darin nach dem Port fragen kannst und baue das dann in eine Schleife ein.
Antworten