@Sn0w3y: Kommentare sollten dem Leser einen Mehrwert gegenüber dem Code liefern und nicht einfach nur wiederholen was im Code sowieso schon da steht. Bei einem ``setwarnings(False)`` ist es unnötig noch einmal zu kommentieren, dass dort die Warnungen ausgeschaltet werden. Faustregel: Nicht kommentieren was der Code macht, denn das kann man ja am Code selbst ablesen, sondern *warum* er das so macht. Natürlich nur falls das nicht offensichtlich ist, oder einfach nachgelesen werden kann.
Schlimm ist wenn der Kommentar dem Code widerspricht. Denn dann weiss der Leser nicht was falsch ist, der Code oder der Kommentar. Wenn man also kommentiert, dass irgend etwas mit Pin 23 gemacht wird, dann sollte der Code stattdessen nichts mit Pin 22 machen. Das ist hier nochmal verwirrend weil man nicht weiss ob der Kommentar und Code nicht vielleicht doch zusammen passen weil in der Informatik oft bei 0 angefangen wird zu zählen, so dass man hier auch denken könnte 22 steht tatsächlich für Pin 23 weil 0 für Pin 1 stehen könnte.
Warum schaltest Du die Warnungen aus? Die gibt es ja nicht ohne Grund. Wenn die etwas ausgeben, dann in aller Regel weil man nicht sauber programmiert hat, also zum Beispiel nicht dafür sorgt, dass `GPIO.cleanup()` aufgerufen wird, bevor das Programm verlassen wird. Statt Warnungen zu deaktivieren sollte man besser die Ursache der Warnungen beseitigen.
Für das Rechnen mit Zeiten bietet sich das `datetime`-Modul an.
``self = root`` ist eine eigenartige Zeile. Der Name `self` hat in Python eine besondere Bedeutung. Unnötig ist die Zeile zu dem auch, denn man kann doch einfach `root` verwenden.
Dateien die man öffnet, sollte man auch wieder schliessen. Die ``with``-Anweisung ist da sehr praktisch.
Werte die sich im Code wiederholen, aber auch solche die zwar für den Programmlauf fest sind, die man aber vielleicht irgendwann einmal ändern können möchte, sollte man am Anfang des Programms als Konstanten festlegen. Das betrifft zum Beispiel Pin-Nummern und Dateinamen.
Die Programmlogik und die GUI sollte man sauber trennen, so das man die Programmlogik auch ohne die GUI verwenden und testen kann.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import Tkinter as tk
from datetime import datetime as DateTime, timedelta as TimeDelta
from itertools import count
from RPi import GPIO
START_PIN = 22
LOG_FILENAME = 'log.txt'
class Stopwatch(object):
def __init__(self, log_filename):
self.log_filename = log_filename
self.part_counter = count()
self.start_time = None
self._elapsed_time = TimeDelta()
def __str__(self):
minutes, seconds = divmod(self.elapsed_time.total_seconds(), 60)
hours, minutes = divmod(minutes, 60)
return '{0:02d}:{1:02d}:{2:02d}'.format(hours, minutes, seconds)
@property
def is_running(self):
return self.start_time is not None
@property
def elapsed_time(self):
if self.is_running:
return DateTime.now() - self.start_time
else:
return self._elapsed_time
def start(self):
if not self.is_running:
self.start_time = DateTime.now()
def stop(self):
if self.is_running:
self._elapsed_time = self.elapsed_time
self.start_time = None
with open(self.log_filename, 'a') as log_file:
log_file.write(
'Teil #{0} {1}\n'.format(next(self.part_counter), self)
)
class StopwatchUI(tk.Frame):
def __init__(self, parent, stopwatch):
tk.Frame.__init__(self, parent)
self.stopwatch = stopwatch
self.time_label = tk.Label(parent, font=('Helvetica', 150))
self.time_label.pack()
tk.Button(parent, text='Start', command=self.stopwatch.start).pack()
tk.Button(parent, text='Stop', command=self.stopwatch.stop).pack()
tk.Button(parent, text='Quit', command=self.quit).pack()
self._update_display()
def _update_display(self):
self.time_label['text'] = str(self.stopwatch)
self.after(10, self._update_display)
def main():
try:
GPIO.setmode(GPIO.BCM)
GPIO.setup(START_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
root = tk.Tk()
root.overrideredirect(True)
width, height = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry('{0}x{1}+0+0'.format(width, height))
root.wm_title('Stoppuhr')
stopwatch = Stopwatch(LOG_FILENAME)
GPIO.add_event_detect(START_PIN, GPIO.FALLING, stopwatch.start)
stopwatch_ui = StopwatchUI(root, stopwatch)
stopwatch_ui.pack()
root.mainloop()
finally:
GPIO.cleanup()
if __name__ == '__main__':
main()
Wobei ich nicht explizit selbst einen Thread starte der auf die fallende Flanke an dem GPIO-Pin wartet, sondern das der Funktion `add_event_detect()` aus dem `GPIO`-Modul überlasse. Und das funktioniert auch nur deswegen so einfach, weil der Rest des Programms so geschrieben ist, wie er das im Moment ist. Das Thema nebenläufige Programmierung, und die ganzen Probleme die dabei auftreten können, ist recht komplex. Wenn man beispielsweise die Aktualisierung der Anzeige effizienter machen will, so dass die nur stattfindet wenn die Uhr auch tatsächlich läuft, dann wird das alles ein wenig komplizierter.