Seite 1 von 1
[Bottle] HTML automatisch aktualisieren
Verfasst: Dienstag 7. Oktober 2014, 12:53
von lackschuh
Hallo
In meiner Bottle-App habe ich eine Funktion eingebaut, welche mir einen Bewegungssensor ausliest.
Code: Alles auswählen
return template('info', username=username, alarm=alarm, motion=motion)
Code: Alles auswählen
<p class="bg-primary">Bewegnung Nr. <b><test_tag>{{motion}}</test_tag></b> erkannt</p>
Welche Möglichkeit habe ich, dass zB ''{{motion}}'' im Template automatisch aktualisiert wird ohne dass ein User die Seite neu laden muss?
Mit jQuery kenne ich mich noch nicht gut aus, darum wäre ich für ein paar Tipps dankbar, falls dies nur mit JS möglich ist.
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Dienstag 7. Oktober 2014, 13:00
von BlackJack
@lackschuh: Im Template kannst Du nichts mehr nachträglich aktualisieren. Daraus wird ja HTML erstellt und an den Browser gesendet. Damit ist das erledigt.
Du müsstest eine eigene URL zur Verfügung stellen über die man den Wert des Sensors abfragen kann. Entweder als HTML-Fragment oder als JSON. Und dann fragst Du auf der Clientsseite per JavaScript regelmässig diesen Wert ab und baust ihn an der passenden Stelle in die angezeigte Webseite ein.
<test_tag> ist übrigens kein gültiges HTML.
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Dienstag 7. Oktober 2014, 13:11
von lackschuh
Danke für die Info. Ich werde mich diesbezüglich schlau machen und mich wieder melden.
<test_tag> ist übrigens kein gültiges HTML.
Ich hab da mit jQuery experimentiert und diesbezüglich ist es soweit ich das bis jetzt verstanden habe Wurst, wie die Tags benannt werden. In der 'test.js' bekomme ich den Inhalt mit '$('test_tag').html()' aber das ist/war der falsche Weg.
mfg
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Dienstag 7. Oktober 2014, 13:16
von BlackJack
@lackschuh: Das jQuery das Wurst ist, ist trotzdem kein Grund kaputtes HTML zu erzeugen. Wenn man Elemente eindeutig indentifizieren möchte, dann verpasst man denen ein `id`-Attribut. Und wenn man an der Stelle nicht ein ”natürliches” Element hat, dann kennt HTML <span> und <div> für Inline- oder Blockelemente die man verwenden kann statt kaputtes HTML zu erzeugen.
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 07:10
von lackschuh
Hallo
Beim Versuch der Umsetzung deines ersten Vorschlags (URL zur Verfügung stellen) bin ich auf ein Problem gestoßen, welches mich seit gestern Abend beschäftigt. Und zwar zähle ich die Anzahl an erkannten Bewegungen. Um 'global' zu vermeiden, zähle ich mittels Decorator die Anzahl der Aufrufe der Funktion. Nun aber ist es so, dass zB der User die URL x-beliebig aufrufen bzw die Seite neu laden kann und so werden die Werte verfälscht.
Leider fällt mich nichts sinnvolles ein, wie ich dies lösen könnte. Ich bräuchte die Daten nur temporär oder soll ich die Daten trotzdem in ein JSON File speichern?
PS:
Die Fake-Tag habe ich durch eine 'id' ersetze.
Code: Alles auswählen
@app.route('/info', method='POST')
def info_post():
GPIO.setup(PIR_PIN, GPIO.IN)
alarm = int(request.forms.get('checkbox'))
if alarm == 1:
# http://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/
GPIO.add_event_detect(PIR_PIN, GPIO.RISING, callback=bewegnung)
while alarm == 1:
time.sleep(1)
elif alarm == 0:
print("Quit")
GPIO.cleanup()
@app.route('/bewegung')
def get_motion_data():
motion_data = bewegnung()
return motion_data
@counter
def bewegnung():
return 'Bewegung erkannt Nr. {0} am: {1}'.format(bewegnung.count, time.strftime("%d-%B-%Y %H:%M:%S"))
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 08:26
von Sirius3
@lackschuh: verstehe nicht, was eine Bewegung mit der Anzahl der Aufrufe einer Funktion zu tun haben soll :K
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 08:56
von lackschuh
Moin,
Wenn der PIR-Sensor eine Bewegung erkennt, dann wird die Funktion bewegnung() (''callback=bewegnung'') aufgerufen. Nun möchte ich die Anzahl an Bewegungen zählen. Auf der Konsole funktioniert das auch ganz gut, weil da niemand eingreifen kann. Mir fehlt aber jetzt die Logik, wie ich das in Bottle umsetze. Am ehesten schreibe ich eine Funktion, wo die Daten, also ''Bewegung Nr. {0} am: {1}'.format(count, time.strftime("%d-%B-%Y %H:%M:%S"))' in eine JSON Datei geschreiben werden und eine zweite Funktion, welche das JSON mittels route/URL dem jQuery zur Verfügung stellt.
Analog dazu:
Code: Alles auswählen
count = 0
def bewegnung():
global count
count = count + 1
return 'Bewegung Nr. {0} am: {1}'.format(count, time.strftime("%d-%B-%Y %H:%M:%S"))
Konsolenprogramm:
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function
from functools import wraps
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
PIR_PIN = 8
GPIO.setup(PIR_PIN, GPIO.IN)
def counter(func):
@wraps(func)
def tmp(*args, **kwargs):
tmp.count += 1
return func(*args, **kwargs)
tmp.count = 0
return tmp
@counter
def bewegnung(PIR_PIN):
print('Bewegung erkannt {0}'.format(bewegnung.count))
time.sleep(2)
def main():
try:
GPIO.add_event_detect(PIR_PIN, GPIO.RISING, callback=bewegnung)
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Quit")
GPIO.cleanup()
if __name__ == '__main__':
main()
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 09:14
von Sirius3
@lackschuh: Du brauchst das nicht in eine Datei zu schreiben, eine Variable reicht auch schon.
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 10:26
von BlackJack
@lackschuh: Das so einzubauen sieht mir ein wenig gefährlich aus. Der Benutzer, oder sogar mehrere Benutzer können die `/info`-URL ja durchaus mehr als einmal aufrufen. Bist Du sicher, dass es okay ist `add_event_detect()` mehr als einmal aufzurufen? Und ist das alles threadsafe? Oder garantierst Du dass das immer nur single-threaded laufen wird‽
Das nächste ist die ``while alarm == 1``-Schleife. Da hättest Du auch gleich ``while True`` schreiben können. Wie sollte `alarm` denn dort jemals den Wert ändern wenn alles was *in* der Schleife passiert ein `sleep()` ist? Damit hättest Du einen single-threaded Webserver an der Stelle auch erfolgreich lahm gelegt und einem multi-threaded einen Thread geklaut, weil der fortan in dieser Schleife hängt.
Wir haben hier ein klassisches Beispiel von der Vermischung von Programmlogik und Benutzerinteraktion. Ich würde erst einmal die Programmlogik ohne Webanwendung schreiben. Und da wir hier Zustand haben der über Funktionsaufrufe hinweg existiert, bietet sich da irgendwie eine Klasse an.
Ob der Code am Ende ausschliesslich single-threaded laufen darf, oder auch mit multi-threaded klar kommt, und auf keinen Fall in mehreren Prozessen laufen darf, solltest Du übrigens gut dokumentieren. Würde ich in den „Requirements”-Abschnitt und auch zu Anmerkungen zur Installation schreiben. Und die Webanwendung selber sollte vielleicht die entsprechenden WSGI-Umgebungsvariablen prüfen um gegebenfalls zu meckern wenn die Laufzeitumgebung nicht unterstützt wird.
Mal zwei Absätze aus der mod_wsgi-Dokumentation zum Thema:
„A WSGI application which is not written to take into consideration the different combinations of process and threading models may not be portable and potentially may not be robust when deployed to an alternate hosting platform or configuration.
Although you may not need an application or application component to work under all possible combinations for these values initially, it is highly recommended that any application component still be designed to work under any of the different operating modes. If for some reason this cannot be done due to the very nature of what functionality the component provides, the component should validate if it is being run within a compatible configuration and return a HTTP 500 internal server error response if it isn't.”
Bei Gelegenheit könntest Du alle Vorkommen von `bewegnung` durch `bewegung` ersetzen.

Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 10:27
von lackschuh
@Sirius3
Aber wie ohne 'global'?
Mit 'global' geht es schon:
Code: Alles auswählen
counter = 0
def bewegung(PIR_PIN):
global counter
counter = counter + 1
print 'Bewegung %d' % counter
@app.route('/bewegung')
def get_motion_data():
global counter
return 'Bewegung erkannt Nr. {0}'.format(counter)
EDIT:
Hab BlackJack's Beitrag noch nicht gesehen.
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 10:39
von BlackJack
@lackschuh: Mit dem Dekorator hast Du doch ein Attribut auf dem Funktionsobjekt. Warum willst Du das durch ein ``global`` ersetzen?
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 15:16
von lackschuh
BlackJack hat geschrieben:Der Benutzer, oder sogar mehrere Benutzer können die `/info`-URL ja durchaus mehr als einmal aufrufen.
Ich hab ein Login mittels 'bottle.ext.sqlite' gebaut, so dass nur berechtigte User den Sensor an- oder abschalten können.
BlackJack hat geschrieben:Bist Du sicher, dass es okay ist `add_event_detect()` mehr als einmal aufzurufen?
Die Methode wird nur einmal aufgerufen, wenn der Schalter auf 'on' geht also wenn alarm = 1 ist, oder hab ich da was übersehen?
BlackJack hat geschrieben:Mit dem Dekorator hast Du doch ein Attribut auf dem Funktionsobjekt
Ja funktioniert auch gut. Ich werde mir im Laufe der Woche deinen Rat zu Gemüte führen und das Programm nochmals auseinander nehmen (Programmlogik und Benutzerinteraktion) und auch der Namensgebung mehr Beachtung schenken.
Bis jetzt habe ich es mittels jQuery gelöst, dies gleicht aber einer DDOS-Attacke, denn so wird permanent die URL '/bewegung' geladen.
Code: Alles auswählen
@counter
def get_motion(PIR_PIN):
#print 'Bewegung %d' % get_motion.count
print 'Bewegung erkannt Nr. {0} am: {1}'.format(get_motion.count, time.strftime("%d-%B-%Y %H:%M:%S"))
@app.route('/bewegung')
def get_motion_data():
return '{0}'.format(get_motion.count)
@app.route('/info')
def info(db):
global alarm
username = is_logged_in(db)
ip = request.environ.get('REMOTE_ADDR')
if not username:
return template('main', username=False, ip=ip)
else:
return template('info', username=username, alarm=alarm)
@app.route('/info', method='POST')
def info_post():
global alarm
GPIO.setup(PIR_PIN, GPIO.IN)
alarm = int(request.forms.get('checkbox'))
if alarm == 1:
GPIO.add_event_detect(PIR_PIN, GPIO.RISING, callback=get_motion)
while True:
time.sleep(1)
elif alarm == 0:
print 'Exit'
GPIO.cleanup()
Code: Alles auswählen
% rebase('layout', title='Seiten-Titel', username=username)
<div class="container">
<h2>Bla Bla 1</h2>
<p class="page-header">Bewegungsmelder ein oder ausschalten</p>
<h4>Alarmanlage: <strong>{{alarm}}</strong></h4>
<input type="checkbox" name="my-checkbox">
<hr>
<p class="bg-primary">Bewegnung Nr. <strong></strong> erkannt</p>
</div>
Code: Alles auswählen
$("p.bg-primary").find( "strong" ).load( "bewegung" );
var refreshId = setInterval(function() {
$("p.bg-primary").load('bewegung');
}, 1000);
PS:
1x 'global' hab ich noch in Verwendung

Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 15:42
von EyDu
lackschuh hat geschrieben:BlackJack hat geschrieben:Bist Du sicher, dass es okay ist `add_event_detect()` mehr als einmal aufzurufen?
Die Methode wird nur einmal aufgerufen, wenn der Schalter auf 'on' geht also wenn alarm = 1 ist, oder hab ich da was übersehen?
Vielleicht kommt ja jemand auf die Idee und ruft die Seite einfach mehrfach auf

Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 15:51
von lackschuh
Wenn die Seite von zwei Usern gleichzeitig aufgerufen wird, dann passiert noch nichts. Sollte dann aber beide den Schalter betätigen zum Einschalten des Sensors, dann bekommt der langsamere folgende Meldung bzw auf der Konsole wird folgendes ausgegeben:
Code: Alles auswählen
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/bottle.py", line 862, in _handle
return route.call(**args)
File "/usr/local/lib/python2.7/dist-packages/bottle.py", line 1729, in wrapper
rv = callback(*a, **ka)
File "app.py", line 283, in info_post
GPIO.add_event_detect(PIR_PIN, GPIO.RISING, callback=get_motion)
RuntimeError: Conflicting edge detection already enabled for this GPIO channel
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Mittwoch 8. Oktober 2014, 16:01
von BlackJack
@lackschuh: Diese Ausnahme sollte man ja eigentlich verhindern.
Vielleicht habe ich mich mit dem ``while True:`` missverständlich ausgedrück: Das ist totaler Unsinn. Du blockierst damit einfach nur einen Thread in einem multi-threaded Webserver, oder den gesamten Webserver bei einem single-threaded Webserer. Ein multi-process Server funktioniert mit der Anwendung gar nicht, weil da weder der Counter einmalig ist, noch der Code der auf die Hardware zugreift.
Das mit der ständigen Abfrage ist der Weg wie man das (noch) löst. Zumindest bis sich Websockets durchgesetzt haben.
Das HTML- und JavaScript-Fragment verwendet jetzt doch gar keine ID um das Element zu identifizieren‽
Wenn man sowieso schon regelmässig die Anzahl der Bewegungen abfragt, könnte man auch gleich den Zustand des Schalters mitliefern. Dann könnte man den auch immer aktualisieren.
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Donnerstag 9. Oktober 2014, 15:41
von lackschuh
Hallo BlackJack
Das mit dem 'while True' ist einleuchtend. Ich habe dies stumpf 1:1 aus meinem Konsolen-Programm so übernommen. Auch wurde nun eine ID anstelle eines Tags im Template eingefügt. Ich habe noch eine Variable 'global alarm' in Verwendung, weil ich so den Zustand auch nach dem Schliessen des Browser speichern kann. Da ich eh schon eine DB habe, wäre es wohl besser, dies direkt in die DB zu schreiben und abzufragen. (?)
BlackJack hat geschrieben:Wenn man sowieso schon regelmässig die Anzahl der Bewegungen abfragt, könnte man auch gleich den Zustand des Schalters mitliefern. Dann könnte man den auch immer aktualisieren.
Der Sensor liefert nur 1 oder 0 zurück. Also wenn eine Bewegung erkannt wird, dann ist der GPIO Input ("GPIO.input(PIR_PIN)") für ca. 1/2 Sekunde auf 1 und dann wieder auf 0.
Code: Alles auswählen
def get_time_of_motion():
time_of_motion = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return time_of_motion
def counter(func):
@wraps(func)
def tmp(*args, **kwargs):
tmp.count += 1
tmp.motion_time = get_time_of_motion()
tmp.motion_liste = create_motion_list()
return func(*args, **kwargs)
tmp.count = 0
tmp.motion_time = '0:0:0:0'
tmp.motion_liste = []
return tmp
motion_list = []
def create_motion_list():
motion_list.append([get_motion.count, get_motion.motion_time])
return motion_list
@app.route('/info', method='POST')
def info_post():
global alarm
GPIO.setup(PIR_PIN, GPIO.IN)
alarm = int(request.forms.get('checkbox'))
if alarm == 1:
print 'Start PIR-Sensor'
GPIO.add_event_detect(PIR_PIN, GPIO.RISING, callback=get_motion)
elif alarm == 0:
print 'Exit'
GPIO.cleanup()
Code: Alles auswählen
% rebase('layout', title='Informations', username=username)
<div class="container">
<h2>Bla Bla 1</h2>
<p class="page-header">Bewegungsmelder ein oder ausschalten</p>
<h4>Alarmanlage: <status>{{alarm}}</status></h4>
<input type="checkbox" name="my-checkbox">
<hr>
<div id="my_table"></div>
</div>
Code: Alles auswählen
$("#my_table").load( "bewegung" );
var refreshId = setInterval(function() {
$("#my_table").load( "bewegung" );
}, 1000);
Ausgabe:
Code: Alles auswählen
Bewegung Datum
1 2014-10-09 14:37:17
2 2014-10-09 14:37:26
3 2014-10-09 14:37:40
4 2014-10-09 14:38:10
5 2014-10-09 14:38:29
6 2014-10-09 14:38:46
7 2014-10-09 14:39:04
8 2014-10-09 14:39:12
EDIT:
Ups, hab gerade noch gesehen, dass im Template noch ein Tag '<status>' anstelle einer ID ist. Muss ich noch ändern.
Re: [Bottle] HTML automatisch aktualisieren
Verfasst: Donnerstag 9. Oktober 2014, 16:21
von BlackJack
@lackschuh: Mit Schalter meine ich den „Alarm ist an/aus”-Schalter, also Dein globales `alarm`. Nicht die Hardware die da abgefragt wird.
Du übertreibst das echt mit dem Dekorator und was Du da alles als Attribute an die Funktion heftest. Eine Klasse wäre hier glaube ich deutlich verständlicher.
Und die neue globale Variable `motion_list` ist auch unschön. Das `motion_liste`-Attribut wird gesetzt aber überhaupt nicht verwendet!?
Das Anhängen von konkreten Typen an Namen ist auch nicht gut. Vielleicht ändert man ja irgendwann mal den Typ, zum Beispiel zu einer `collections.deque` damit man nicht *alle* Bewegungen sammelt sondern nur die letzten x Bewegungen. Und dann ist der Name plötzlich falsch und man muss den überall ändern.
Die '/info'-URL gibt gar nichts zurück‽ Würde ich bei dem Namen aber irgendwie erwarten.