Beispiele Ampel Aufzug Digitaluhr Parkplatzschranke Jalousie Garagentor Buzzer
Pfad: Startseite / Fächer / Informatik / Python / OOP / Beispiele / Ampel
Autor: mk
24.01.2010 11:26:12
3354
Ampel

Ampeln zeichnen

Nach dem Vorbild einer früheren Delphi-Realisierung sollen Methoden zum Zeichnen von Ampeln entwickelt werden.

Ampeln

Die Programme ampel0.py und ampel2.py zeigen Lösungsmöglichkeiten auf. Beide Programme haben eine strikte MVC-Struktur. Während ampel0.py eine eigene Klasse 'Ampelcanvas' einführt, die die TKinter-Canvas spezialisiert, verwendet ampel2.py Klassenmethoden, die eine Referenz der Canvas benötigen, worauf sie zeichnen sollen.

Einfache Ampel mit 'Handsteuerung'

Hand-Ampel ampel3.py

Zustandsdiagramm ampel3.state.violet

Die Zustände werden durch Strings beschrieben, die in einem unverändlichen (immutable) Tupel zusammengefasst sind. Durch das Tupel erhalten die Zustände Nummern. Die Nummer des aktuellen Zustands steht in dem Attribut zustand.

# model

class Model(object):
    def __init__(self):
        self.zustaende = ('rot','gelbrot','grün','gelb')
        self.zustand = 0

    def weiter(self):
        self.zustand = (self.zustand+1)%4

Der Controller 'hängt' die Methode weiter als Callback bei der Erzeugung von view ein, dh. ein Druck auf den Button löst einen Aufruf von weiter aus. weiter gibt die Aufforderung an model weiter und erfragt den aktuellen Zustand. Je nach Zustand wird dann die Ampel in view neu gezeichnet. Der Moment für ein Neuzeichnen ist richtig gewählt, da model gerade den Zustand gewechselt hat.

# controller

class Controller(object):
    def __init__(self):
        self.model = Model()
        self.view = View(self.weiter) # Ereignisbehandlung einhängen
        Ampel.zeichne(self.view.c,75,20,20,True,False,False)
        self.view.mainloop()

    def weiter(self):
        self.model.weiter()           # weiterleiten an model
        zustand = self.model.zustand  # Zustand von model erfragen
        # je nach Zustand Ampel in view neu zeichnen
        if zustand == 0:
            Ampel.zeichne(self.view.c,75,20,20,True,False,False)
        elif zustand == 1:
            Ampel.zeichne(self.view.c,75,20,20,True,True,False)
        elif zustand == 2:
            Ampel.zeichne(self.view.c,75,20,20,False,False,True)
        elif zustand == 3:
            Ampel.zeichne(self.view.c,75,20,20,False,True,False)

Timergesteuerte Ampel

Soll die Ampel selbstständig laufen, so benötigt sie einen Zeitgeber, einen Timer. Ein Timer ist ein sehr einfach aufgebauter Thread. Nach Ablauf der Zeit interval wird die Funktion routine aufgerufen. Das was ein Thread nach seinem Start tun soll, bestimmt die Methode run. Dh. run muss überschrieben werden. In unserem Fall wird der Thread zunächst mit sleep schlafengelegt und führt dann die Funktion routine aus. Gestartet wird ein Thread durch die Methode start(). Im vorliegenden Fall erzeugt die Routine weiter am Ende einen neuen Thread, der wieder weiter aufruft. Es entsteht also eine Endlosschleife. Bitte beachten, wie der Timer auf die jeweils aktuelle Phasenlänge programmiert wird.

# model

import threading,time

class Timer(threading.Thread):
    def __init__(self,interval,routine):
        threading.Thread.__init__(self)
        self.interval = interval
        self.routine = routine

    def run(self):
        time.sleep(self.interval)
        self.routine()


class Model(object):
    def __init__(self):
        self.zustaende = ('rot','gelbrot','grün','gelb')
        self.phasenlaengen = (5,0.5,5,1)
        self.zustand = 0
        self.timer = Timer(self.phasenlaengen[self.zustand],self.weiter)
        self.timer.start()

    def weiter(self):
        self.zustand = (self.zustand+1)%4
        self.timer = Timer(self.phasenlaengen[self.zustand],self.weiter)
        self.timer.start()

Timer-Ampel

Wenn model einen selbstständig laufenden Timer enthält, kann der Controller nicht mehr wissen, wann die Ampel in view neu gezeichnet werden soll. Eine mögliche Lösung ist, dass ein callback von view periodisch den Zustand von model abfragt (polling) und die Ampel entsprechend zeichnet.

ampel4.py, ampel5.py

Was ist eigentlich an ampel4.py so schlecht? Schaue dir die CPU-Auslastung an!

Eine weitere Möglichkeit besteht darin, auf das Polling zu verzichten und stattdessen in model ein callback OnChange einzubauen. Das Neuzeichnen der Ampel sollte dann aber threadsicher gemacht werden.

class Model(object):
    def __init__(self,OnChange):
        self.zustaende = ('rot','gelbrot','grün','gelb')
        self.phasenlaengen = (5,0.5,5,1)
        self.zustand = 0
        self.OnChange = OnChange
        self.timer = Timer(self.phasenlaengen[self.zustand],self.weiter)
        self.timer.start()

    def weiter(self):
        self.zustand = (self.zustand+1)%4
        self.OnChange()
        self.timer = Timer(self.phasenlaengen[self.zustand],self.weiter)
        self.timer.start()

ampel6.py

Einfache Fußgängerbedarfsampel

Fußgängerampel Zustandsdiagramm

ampel7.pyw

Fußgängerbedarfsampel mit externem View

Ampelmodell Scaltung ampel8.pyw

Es ist reizvoll, die serielle Schnittstelle zur Ansteuerung einer externen Ampel zu verwenden. In folgendem Programm wird alle 20ms der Zustand des CTS-Eingangs abgefragt und gegebenenfalls ein Callback ausgelöst. Der Destruktor des Views sorgt dafür, dass auch bei einem unsauberen Beenden des Programms die Schnittstelle wieder geschlossen wird.

# -*- coding: iso-8859-1 -*-
# mk, 22.4.09

# model

import threading,time

class Timer(threading.Thread):
    def __init__(self,interval,routine):
        threading.Thread.__init__(self)
        self.interval = interval
        self.routine = routine

    def run(self):
        time.sleep(self.interval)
        self.routine()


class Model(object):
    def __init__(self,OnChange):
        self.zustaende = ('dauergrün','gelb','rot','gelbrot','grün')
        self.phasenlaengen = (0,0.5,5,1,5)
        self.zustand = 0
        self.OnChange = OnChange

    def weiter(self):
        self.zustand = (self.zustand+1)%5
        if self.OnChange != None:
            self.OnChange()
        if self.zustand != 0:
            self.timer = Timer(self.phasenlaengen[self.zustand],self.weiter)
            self.timer.start()

# view

import serial

class View(object):
    def __init__(self,cbTaste):
        self.cb = cbTaste
        self.s = serial.Serial(0)
        self.t = Timer(0.02,self.poll)
        self.t.start()

    def __del__(self):                   # schließt sicher die Schnittstelle
        self.s.close()

    def rot(self,level):
        self.s.setRTS(level)

    def gelb(self,level):
        self.s.setBreak(level)

    def gruen(self,level):
        self.s.setDTR(level)

    def poll(self):
        if self.s.getCTS():
            if self.cb != None:
                self.cb()
        self.t = Timer(0.02,self.poll)
        self.t.start()

# controller

class Controller(object):
    def __init__(self):
        self.model = Model(self.update)
        self.view = View(self.taste)
        self.z = None
        self.update()

    def update(self):
        za = self.z
        self.z = self.model.zustand
        if za != self.z:
            if self.z == 0:
                self.view.rot(0)
                self.view.gelb(0)
                self.view.gruen(1)
            elif self.z == 1:
                self.view.rot(0)
                self.view.gelb(1)
                self.view.gruen(0)
            elif self.z == 2:
                self.view.rot(1)
                self.view.gelb(0)
                self.view.gruen(0)
            elif self.z == 3:
                self.view.rot(1)
                self.view.gelb(1)
                self.view.gruen(0)
            elif self.z == 4:
                self.view.rot(0)
                self.view.gelb(0)
                self.view.gruen(1)

    def taste(self):
        self.z = self.model.zustand
        if self.z == 0:
            self.model.weiter()

# Hauptprogramm

c = Controller()

Links