"Zeitschaltuhr"

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.
mpl1337
User
Beiträge: 3
Registriert: Mittwoch 30. Oktober 2013, 18:41

Hallo,

ich blutiger Python anfänger habe ein kleines Problem:

Hardware:

Raspberry -> MCP23017 -> ULN2803 -> 12V Finder Relais für hutschiene inkl. Freilaufdiode

Das ganze dient als Terrarium Steuerung. Nun...

Heute bin ich nach der Arbeit nachhause gekommen und musste feststellen das alles aus ist.

Ich bin mir nicht Sicher ob meine If abfragen so richtig gestaltet sind oder nicht...

Code: Alles auswählen

#!/usr/bin/python

from Adafruit_I2C import Adafruit_I2C
import smbus
import time
import datetime


///unnötiges entfern

if __name__ == '__main__':

    Relais = Adafruit_MCP230XX(address = 0x20, num_gpios = 16) # MCP23017

    # Set pins 0, 1 and 2 to output (you can set pins 0..15 this way)
    Relais.config(0, Relais.OUTPUT)
    Relais.config(1, Relais.OUTPUT)
    Relais.config(2, Relais.OUTPUT)
    Relais.config(8, Relais.OUTPUT)
    Relais.config(9, Relais.OUTPUT)
    Relais.config(10, Relais.OUTPUT)
    Relais.config(11, Relais.OUTPUT)
    Relais.config(12, Relais.OUTPUT)
    Relais.config(13, Relais.OUTPUT)
    Relais.config(15, Relais.OUTPUT)
    
    
    
    
    HCIlockan = 0
    HCIlockaus = 0
    LSlockan = 0
    LSlockaus = 0
    Spotlockan = 0
    Spotlockaus = 0
    Halogenlockan = 0
    Halogenlockaus = 0
    Filterlockan = 0
    Filterlockaus = 0
    Wasserfalllockan = 0
    Wasserfalllockaus = 0
    LED1lockan = 0
    LED1lockaus = 0
    LED2lockan = 0
    LED2lockaus = 0
    LED3lockan = 0
    LED3lockaus = 0
    
    # Set pin 3 to input with the pullup resistor enabled
    # mcp.config(3, mcp.INPUT)
    # mcp.pullup(3, 1)

    # Read input pin and display the results
    # print "Pin 3 = %d" % (mcp.input(3) >> 3)

    # Python speed test on output 0 toggling at max speed
    while (True):
      #Relais.output(9, 1)# Pin 0 High        
        
        now = datetime.datetime.now()
        
        HCIAn = now.replace(hour=6, minute=0, second=0, microsecond=0)
        HalogenAn = now.replace(hour=6, minute=2, second=0, microsecond=0)
        LED1An = now.replace(hour=6, minute=2, second=0, microsecond=0)
        LED2An = now.replace(hour=6, minute=2, second=0, microsecond=0)
        LED3An = now.replace(hour=6, minute=2, second=40, microsecond=0)
        FilterAn = now.replace(hour=6, minute=3, second=0, microsecond=0)
        WasserfallAn = now.replace(hour=6, minute=3, second=10, microsecond=0)
        LSAn = now.replace(hour=6, minute=10, second=0, microsecond=0)
        SpotAn = now.replace(hour=6, minute=12, second=0, microsecond=0)
      
      
      
        WasserfallAus = now.replace(hour=20, minute=0, second=0, microsecond=0)
        HCIAus = now.replace(hour=21, minute=0, second=0, microsecond=0)
        LSAus = now.replace(hour=21, minute=30, second=0, microsecond=0)
        SpotAus = now.replace(hour=21, minute=35, second=0, microsecond=0)
        FilterAus = now.replace(hour=21, minute=36, second=0, microsecond=0)
        LED1aus = now.replace(hour=21, minute=55, second=0, microsecond=0)
        LED2aus = now.replace(hour=21, minute=55, second=0, microsecond=0)
        LED3aus = now.replace(hour=21, minute=55, second=0, microsecond=0)
        HalogenAus = now.replace(hour=21, minute=55, second=10, microsecond=0)
      
      
      
        RegenAn = now.replace(hour=10, minute=0, second=0, microsecond=0)
        RegenAus = now.replace(hour=21, minute=0, second=0, microsecond=0)


    # <---- 12V Relais beginn  ---->


        
        if now >= HCIAn and now >= HCIAus and HCIlockaus == 0:
            HCIlockaus = 1
            Relais.output(8,0)
            print now
            print "HCI Aus"
            HCIlockan = 0
        
        if now <= HCIAus and now >= HCIAn and HCIlockan == 0:
            HCIlockan = 1
            Relais.output(8,1)
            print now
            print "HCI An"
            HCIlockaus = 0
        
        
        if now >= LSAn and now >= LSAus and LSlockaus == 0:
            LSlockaus = 1
            Relais.output(9,0)
            print now
            print "LS Aus"
            LSlockan = 0
        
        if now <= LSAus and now >= LSAn and LSlockan == 0:
            LSlockan = 1
            Relais.output(9,1)
            print now
            print "LS An"
            LSlockaus = 0

        if now >= SpotAn and now >= SpotAus and Spotlockaus == 0:
            Spotlockaus = 1
            Relais.output(10,0)
            Relais.output(15,0)
            print now
            print "Spot Aus"
            Spotlockan = 0

        if now <= SpotAus and now >= SpotAn and Spotlockan == 0:
            Spotlockan = 1
            Relais.output(10,1)
            Relais.output(15,1)
            print now
            print "Spot An"
            Spotlockaus = 0
        
        if now >= HalogenAn and now >= HalogenAus and Halogenlockaus == 0:
            Halogenlockaus = 1
            Relais.output(11,0)
            Relais.output(0,0) 
            Relais.output(1,0) 
            Relais.output(2,0) 
            print now
            print "Halogen Aus"
            Halogenlockan = 0
        
        if now <= HalogenAus and now >= HalogenAn and Halogenlockan == 0:
            Halogenlockan = 1
            Relais.output(11,1)
            Relais.output(0,1) 
            Relais.output(1,1) 
            Relais.output(2,1) 
            print now
            print "Halogen An"
            Halogenlockaus = 0        
      
        if now >= FilterAn and now >= FilterAus and Filterlockaus == 0:
            Filterlockaus = 1
            Relais.output(12,0)
            print now
            print "Filter Aus"
            Filterlockan = 0

        if now <= FilterAus and now >= FilterAn and Filterlockan == 0:
            Filterlockan = 1
            Relais.output(12,1)
            print now
            print "Filter An"
            Filterlockaus = 0      
      
        if now >= WasserfallAn and now >= WasserfallAus and Wasserfalllockaus == 0:
            Wasserfalllockaus = 1
            Relais.output(13,0)
            print now
            print "Wasserfall Aus"
            Wasserfalllockan = 0
            
        if now <= WasserfallAus and now >= WasserfallAn and Wasserfalllockan == 0:
            Wasserfalllockan = 1
            Relais.output(13,1)
            print now
            print "Wasserfall An"
            Wasserfalllockaus = 0        
        
    # <---- 12V Relais Ende ---->




    # <---- 5V Relais beginn ---->
    
"""    
        if now >= LED1An and now >= LED1aus and LED1lockaus == 0:
            LED1lockaus = 1
            Relais.output(0,0) //LED 1
            print now
            print "LED1 Aus"
            LED1lockan = 0

        if now <= LED1aus and now >= LED1An and LED1lockan == 0:
            LED1lockan = 1
            Relais.output(0,1)
            print now
            print "LED1 An"
            LED1loclaus = 0 
            
            
        if now >= LED2An and now >= LED2aus and LED2lockaus == 0:
            LED2lockaus = 1
            Relais.output(1,0)
            print now
            print "LED2 Aus"
            LED2lockan = 0

        if now <= LED2aus and now >= LED2An and LED2lockan == 0:
            LED2lockan = 1
            Relais.output(1,1)
            print now
            print "LED2 An"
            LED2loclaus = 0 
            
            
        if now >= LED3An and now >= LED3aus and LED3lockaus == 0:
            LED3lockaus = 1
            Relais.output(2,0)
            print now
            print "LED3 Aus"
            LED3lockan = 0

        if now <= LED3aus and now >= LED3An and LED3lockan == 0:
            LED3lockan = 1
            Relais.output(2,1)
            print now
            print "LED3 An"
            LED3loclaus = 0     
"""


    # <---- 5V Relais Ende ---->



so: wo steckt der Wurm?

Ich möchte das HCI z.b. um 10 an geht um 21 ausgeht und wenn ich das Script mal während des Tages abschalte wegen änderungen und sich von der Zeit was geändert hat das beim zuschalten der gewollte zustand geschaltet wird und nicht erst bei erreichen der nächsten erfolgreichen zeitabfrage

ansonsten könnte ich die zeitabfragen ja so gestalten

if now == WasserfallAn and Wasserfalllockaus == 0:
//anschalten

if now == WasserfallAus and Wasserfalllockan == 0:
//abschalten

aber dann hab ich eben das was ich nicht möchte

könnt ihr mir bitte weiterhelfen?


vielen dank schonmal =)
BlackJack

@mpl1337: Den konkreten Fehler möchte ich dem Quelltext nicht suchen. Selbst wenn man den zum Laufen bekäme ist der zu unübersichtlich, enthält zu viel kopierten und leicht angepassten Quelltext, und ist zu unflexibel.

Als erstes sollte man den Quelltext unter der ``if __main__``-Abfrage mal in eine Funktion stecken um jedgliche Beeinflussung „von” aussen auszuschliessen. All die Namen sind ja auf Modulebene definiert und damit kann auch in dem Teil den Du durch den (syntaktisch falschen) Kommentar ersetzt hast, zumindest potentiell etwas an dem Zustand des Hauptpropgramms verändern von dem man bei dem gezeigte Programmteil nichts sieht.

Die Namen halten sich nicht an die Konventionen aus dem Style Guide for Python Code. Das macht es schwieriger den Quelltext zu lesen. Zum Einen weil bestimmte Schreibweisen, zum Beispiel Namen die mit Grossbuchstaben anfangen, bestimmte Erwartungen beim Leser wecken (`Relais` ist ein Datentyp und kein Exemplar von einem Datentyp); zum Anderen muss man bei den wortenohneeinesichtbareTrennung jedesmal beim Lesen selber die Wortgrenzen finden, was im besten Fall nur den Lesefluss hemmt, im schlechten Fall aber sogar zu Missverständnissen führen kann wenn man falsch trennt und etwas liest was da nicht steht.

Kommentare sollten zum Quelltext passen. Wenn sie das nicht tun, dann helfen sie dem Leser nicht. Im Gegenteil. Entweder nimmt er etwas falsches über den Quelltext zum Kommentar an wenn er den Fehler nicht bemerkt, oder er muss plötzlich entscheiden ob der Quelltext oder der Kommentar falsch ist, wenn er den Widerspruch bemerkt. Das ist zugegebermassen nicht so schwer zu erkennen beim setzen der Pins als Ausgabe, dass es mehr als nur Pins 0, 1, und 2 sein *sollen* und auch das in der ``while``-Schleife etwas komplett anderes als ein Geschwindigeitstest durch „togglen” von Pin 0 gemacht wird. Trotzdem sollte man sich so etwas gar nicht erst angewöhnen. Kommentare *müssen* korrekt sein, denn die meisten Programmierer gehen bei Kommentar + Quelltext davon aus, dass im Zweifelsfall der Kommentar stimmt. Zumindest solange sie den Kommentaren vertrauen. Wenn dieses Vertrauen weg ist, dann verlieren Kommentare deutlich an Wert und selbst die, die stimmen verunsichern den Leser.

Code aus Beispielen die mit dem Quelltext nichts zu tun haben, gehören auch nicht als Kommentare in den Quelltext. Die Teile wo das `Adafruit_MCP230XX`-Exemplar noch `mcp` heisst, haben mit dem aktuellen Programm doch *überhaupt* nicht zu tun, oder?

Literale Zeichenketten sind nicht dazu gedacht um Quelltext auszukommentieren. Solche Zeichenketten werden vom Compiler in einen Ausdruck ohne Effekt übersetzt, landen also im Gegensatz zu Kommentaren im übersetzten Bytecode und verbrauchen dort sowohl Speicherplatz als auch Rechenzeit beim Ausführen.

Beim schalten der Pins auf Ausgang fangen im Grunde schon die Code-Wiederholungen durch kopieren und einfügen an. Das einzige was sich in den Zeilen dort jeweils ändert ist die Pin-Nummer. Die ganzen Zeilen liessen sich über eine Schleife über die Pin-Nummern auf zwei reduzieren.

Der Quelltext ist ungünstig organisiert. Das merkt man zum Beispiel daran, dass oft längere Blöcke bestehen die semantisch das selbe machen, aber für ganz unterschiedliche Werte. Das dann aber wiederkehrend in mehreren Blöcken. Das führt dazu das Code für *eine* „Sache” über das komplette Programm verteilt sind. Beispiel die LEDs. In dem Block in dem die ganzen `*lock*`-Namen definiert werden gibt es etwas für die LEDs, in dem Block wo die Einschaltzeiten definiert werden, in dem Block wo die Ausschaltzeiten definiert werden, und dann noch einmal in dem „Block” wo der immer widerkehrende Code-Schnippsel steht der dann letztendlich das Schalten je nach Zeit machen soll. Das erschwert es sowohl „schaltbare Sachen” rückstandslos zu entfernen, weil man irgendwo in einem der Blöcke etwas vergessen kann (`RegenAn`/`RegenAus` zum Beispiel), als auch etwas neues hinzuzufügen, weil man durch den ganzen Code gehen muss und überall etwas hinzufügen muss. Das nicht alle Blöcke die „Sachen” in der gleichen Reihenfolge aufführen, macht es nicht leichter.

Zusammengehörende Werte sollte man in einer Datenstruktur zusammenfassen und nicht an einzelne Namen gebunden über das ganze Programm verteilen. Name, (bekannter) Schaltzustand, Pin-Nummern, und Schaltzeit(en) für jeweils ein „schaltbares Ding” gehören zusammen. Als Daten. Dann kann man eine Funktion zum An-/Ausschalten schreiben statt immer den gleichen Quelltext-Schnippsel zu kopieren und leicht anzupassen. Den Aufruf davon muss man dann auch nicht für jedes schaltbare Ding hinschreiben, sondern man kann eine Schleife über die schaltbaren Dinge schreiben, und den Aufruf im Schleifenkörper nur einmal hinschreiben. Da die Funktion sehr eng zu dem Datensatz gehört, ihn sogar verändern muss, ist das eigentlich ein eigener Datentyp, also eine Klasse mit einer Methode.

Für die einzelnen Schaltzeiten gilt ähnliches. Anfang und Ende gehören zusammen in einen Datentyp. Der zum Beispiel als Operation bietet zu testen ob ein Zeitpunkt innerhalb des Zeitintervalls liegt. Die `__contains__`-Methode bietet sich an, dann kann man den Test als ``now in inverval`` ausdrücken.

Ein schönes Beispiel warum Programmieren durch Kopieren und Einfügen keine gute Vorgehensweise ist sind die ganzen `LED?loclaus`-Fehler im „auskommentierten” Block am Ende der Schleife. Man kopiert auch Fehler immer schön mit und muss die dann an vielen Stellen verbessern und hoffen das man a) alle Stellen erwischt, und b) alle Stellen gleich verändert und die Code-Teile nicht „auseinanderdriften”. Wenn dieser strukturell gleiche Code in einer Funktion oder Methode steckt, statt kopiert zu werden, dann muss man Korrekturen nur an genau einer Stelle vornehmen.

Bei den ganzen `*lock*`-Paaren fällt auf, dass sie ausser nach der Initialisierung immer den entgegengesetzten Wert haben. Also eigentlich redundant sind. Es würde ein Name reichen der drei Werte annehmen kann, also `True` und `False` je nach dem wie das Relais geschaltet ist, und `None` am Anfang wo der Zustand noch unbekannt ist. Statt 0 und 1 sollte man `False` und `True` verwenden, damit der Leser gleich weiss, dass 42 sehr wahrscheinlich kein Wert ist, an den der Name im weiteren Programmverlauf gebunden wird.

Um Bedingungen bei ``if`` & Co braucht man keine Klammern.

Die Schleife sollte nicht mit unverminderter Geschwindigkeit ablaufen. Das lastet die CPU zu 100% aus, ohne dass das wirklich nötig wäre. Man sollte mindestens ein `time.sleep()` mit der hälfte oder einem viertel der kleinsten Genauigkeit für einen Schaltzeitpunkt einfügen. Noch besser: Man berechnet den jeweils nächsten Schaltzeitpunkt und „schläft” bis dahin. Das `sched`-Modul aus der Standardbibliothek kann hier eventuell nützlich sein.
BlackJack

Das ganze mal ohne die Codewiederholungen und mit Daten als Daten und nicht als Code (völlig ungetestet):

Code: Alles auswählen

#!/usr/bin/python
import logging
import time
from datetime import datetime as DateTime, time as Time
from Adafruit_MCP230xx import Adafruit_MCP230XX

# TODO Maybe configure basic logging here.
LOG = logging.getLogger(__name__)

#:
#: Tuples of the form (name, pins, (start, end)).
#:
# 
# TODO Maybe it is useful to extend this structure and the code by more
# than one time interval per relay.
# 
RELAYS = [
    ('Filter', [12], ('06:03:00', '21:36:00')),
    ('Halogen', [11, 0, 1, 2], ('06:02:00', '21:55:00')),
    ('HCI', [8], ('06:00:00', '21:00:00')),
    # ('LED_1', [0], ('06:02:00', '21:55:00')),
    # ('LED_2', [1], ('06:02:00', '21:55:00')),
    # ('LED_3', [2], ('06:02:40', '21:55:00')),
    ('LS', [9], ('06:10:00', '21:30:00')),
    # ('Regen', [?], (?, ?)),
    ('Spot', [10, 15], ('06:12:00', '21:35:00')),
    ('Wasserfall', [13], ('06:03:10', '20:00:00')),
]


def parse_time(string):
    return Time(*map(int, string.split(':')))


class Relay(object):
    def __init__(self, mcp, name, pins):
        self.mcp = mcp
        self.name = name
        self.pins = pins
        self.state = None
        for pin in self.pins:
            self.mcp.config(pin, self.mcp.OUTPUT)

    def switch(self, new_state):
        if self.state != new_state:
            self.state = new_state
            for pin in self.pins:
                self.mcp.output(pin, new_state)
            LOG.info('%s %s', self.name, ['aus', 'an'][new_state])


class TimeSpan(object):
    def __init__(self, start, end):
        if start > end:
            raise ValueError('start must not be greater than end')
        self.start = start
        self.end = end

    def __contains__(self, time):
        return self.start <= time < self.end


def main(): 
    mcp = Adafruit_MCP230XX(address=0x20, num_gpios=16)  # MCP23017
    relays = [
        (Relay(mcp, name, pins), TimeSpan(parse_time(start), parse_time(end)))
        for name, pins, (start, end) in RELAYS
    ]
    while True:
        now = DateTime.now().time()
        for relay, time_span in relays:
            relay.switch(now in time_span)
        time.sleep(0.5)


if __name__ == '__main__':
    main()
mpl1337
User
Beiträge: 3
Registriert: Mittwoch 30. Oktober 2013, 18:41

Hi,

Danke für deine ausführliche Erklärung. Ich möchte nochmals erwähnen das ich blutiger Anfänger bin und erst seit 3 Tagen mit Python zu tun habe.

Ich verstehe nicht all zuviel davon was du mir versuchst zu erklären da das alles neuland für mich ist jedoch werde ich versuchen das was ich verstanden habe umzusetzen

Ich habe mal deinen Code ausprobiert.. aber jedoch tut sich leider nichts

hab noch die 2 klassen von ADAFRUIT ergänzt und noch paar weitere zeilen...

ich glaube mein eigentliches Problem liegt woanders den mein code wirwar hat zwar funktioniert aber im laufe der zeit haben sich die Ausgänge alle auf 0 gesetzt bzw der Portexpander hat sich resetet..

jedoch trotzdem vielen vielen dank für deine Mühe
BlackJack

@mpl1337: Ich habe nicht die notwendige Hardware um das tatsächlich mit Hardware auszuprobieren, aber wenn ich eine „Mock”-Klasse für `Adafruit_MCP230XX` schreibe, dann sehe ich dass die entsprechenden Methoden korrekt aufgerufen werden.

Falls Dir nur die Textausgabe gefehlt hat, muss man den Logger konfigurieren, zum Beispiel mit dieser zusätzlichen Zeile vor der Zuweisung an `LOG`:

Code: Alles auswählen

logging.basicConfig(
    format='%(asctime)s %(message)s', datefmt='%H:%M:%S', level=logging.INFO
)
Mal ein Testlauf bei dem Halogen ausschalten auf '22:46:00' gestellt ist und das um 22:45 gestartet wurde:

Code: Alles auswählen

$ python forum5.py 
__init__ {'num_gpios': 16, 'address': 32}
config (12, 'OUTPUT')
config (11, 'OUTPUT')
config (0, 'OUTPUT')
config (1, 'OUTPUT')
config (2, 'OUTPUT')
config (8, 'OUTPUT')
config (9, 'OUTPUT')
config (10, 'OUTPUT')
config (15, 'OUTPUT')
config (13, 'OUTPUT')
output (12, False)
22:45:18 Filter aus
output (11, True)
output (0, True)
output (1, True)
output (2, True)
22:45:18 Halogen an
output (8, False)
22:45:18 HCI aus
output (9, False)
22:45:18 LS aus
output (10, False)
output (15, False)
22:45:18 Spot aus
output (13, False)
22:45:18 Wasserfall aus
output (11, False)
output (0, False)
output (1, False)
output (2, False)
22:46:00 Halogen aus
Das scheint also soweit zu funktionieren.
mpl1337
User
Beiträge: 3
Registriert: Mittwoch 30. Oktober 2013, 18:41

Sry mein Fehler, Um die Ausgabe gings weniger die wird im End Einsatz sowieso ausgeschaltet,

Ich hatte zwischenzeitlich ein Hardware Fehler eingebaut und nicht bemerkt

aber Jetzt funktioniert es.

Danke =)
loba74
User
Beiträge: 5
Registriert: Freitag 24. Oktober 2014, 23:41

Heya!

Ich hoffe, nach einem Jahr sieht das überhaupt noch jemand... :)

Der Code funktioniert einwandfrei. Ich habe ihn ein wenig umgebaut, damit ich direkt die GPIOs von meinem Raspi via wiringpi ansteuern kann. Allerdings habe ich ein kleines Problem. Ich kann nicht am Abend ein- und morgens wieder ausschalten. Das wäre jedoch für Aussenbeleuchtung sehr interessant.

Nun bin ich ein Noob im Coden und eine absolute Null in Mathe... :oops: Daher 1.: Welchen Wert gibt diese Methode zurück und an wen:

Code: Alles auswählen

def __contains__(self, time):
        return self.start <= time < self.end
Und 2.: Welche Möglichkeiten habe ich grundsätzlich, um Zeiten von z.B. 18:00:00 bis 02:00:00 zu schalten? Klappt das so?

Code: Alles auswählen

def __contains__(self, time):
        if self.start < self.end:
            return self.start <= time < self.end
        else:
            return self.start <= time > self.end
Gruss, Oli
BlackJack

@loba74: Die `__contains__()`-Methode implementiert den ``in``-Operator: https://docs.python.org/2/reference/exp ... st-details
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@loba74: Teste doch Deinen in-Operator für verschiedene Zeiten, dann findest Du schon selbst raus, ob das so klappt. Und "self.start <= time > self.end" ist die Kurzschreibweise für "self.start <= time and self.end < time"
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Hier stand Unsinn.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
loba74
User
Beiträge: 5
Registriert: Freitag 24. Oktober 2014, 23:41

@BlackJack und Sirius3: Danke Euch beiden erst mal für die Antworten - aufgrund des Alters des Threads war ich doch einigermassen erstaunt. Einiges ist mir jetzt wieder klarer geworden.

Ich habe nun den Code mal wie vorher beschrieben angepasst, und siehe da, er schaltet den entsprechenden Kanal tatsächlich um 18.00 Uhr ein - aber bereits um 00:00:00 Uhr wieder aus, und nicht wie gewünscht und auch eingetragen um 02:00:00. Wieso habe ich eben aus Unwissenheitsgründen noch nicht rausgefunden. Ich werde mal weiter pröbeln. Ich find's bestimmt raus, aber wenn mir jemand mit einem Tip die Suchzeit etwas verkürzen möchte, bin ich nicht traurig... :wink:

Gruss, Oli
loba74
User
Beiträge: 5
Registriert: Freitag 24. Oktober 2014, 23:41

Hat geklappt. Sieht zwar unschön aus, funktioniert aber:

Code: Alles auswählen

def __contains__(self, time):                       
        if self.start < self.end:
            return self.start <= time < self.end        # Zyklus innerhalb eines Tages
        elif self.start > self.end:
            if self.start <= time:
                return self.start <= time > self.end    # Zyklus mit Datumssprung: VOR Mitternacht, NACH Einschalten 
            elif self.start > time:
                return self.start > time <= self.end    # Zyklus mit Datumssprung: NACH Mitternacht, VOR Abschalten
        else:
            return False                                # Unbedingtes False wenn Ein == Aus
Danke und Gruss, Oli
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@loba74: wenn Du schon abfragst ob self.start <= time ist, brauchst Du das beim "return" nicht nochmal tun. Und das elif in Zeile 7 ist auch unnötig, weil es genau das Gegenteil der Bedingung in Zeile 5 abfrägt. Der False-Fall wird schon implizit in den beiden anderen Bedingungszweigen erfüllt (eigentlich ja nicht-erfüllt :wink: ).

Das ganze wird also zu

Code: Alles auswählen

def __contains__(self, time):                      
        if self.start <= self.end:
            return self.start <= time < self.end        # Zyklus innerhalb eines Tages
        else:
            if self.start <= time:
                return time > self.end    # Zyklus mit Datumssprung: VOR Mitternacht, NACH Einschalten
            else:
                return time <= self.end    # Zyklus mit Datumssprung: NACH Mitternacht, VOR Abschalten
Jetzt ist ja beim zweiten return so, dass in diesem Zweig ja immer self.end<self.start ist, wenn also time >= self.start ist, gilt laut logik immer time > self.end. Dann kann man beide Fälle noch in ein return packen, in dem man or verwendet:

Code: Alles auswählen

def __contains__(self, time):                      
        if self.start <= self.end:
            return self.start <= time < self.end        # Zyklus innerhalb eines Tages
        else:
            # Zyklus mit Datumssprung: VOR Mitternacht, NACH Einschalten oder NACH Mitternacht, VOR Abschalten
            return self.start <= time or time < self.end
loba74
User
Beiträge: 5
Registriert: Freitag 24. Oktober 2014, 23:41

@ Sirius3:
Tja, das mit der Logik ist so eine Sache... Im Nachhinein ist ja alles so - logisch. Aber kommt Zeit kommt Rat (in diesem Fall dank Deiner / Eurer konstruktiven Hilfe) und Übung macht bekanntlich den Meister (wobei ich kaum jemals über den Lehrlingsstatus hinaus kommen werde...).

Dass ich die letzten beiden Zeilen weglassen könnte, habe ich mir schon fast gedacht. Die Sache mit dem OR habe ich nicht gewusst - oder zumindest erfolgreich verdrängt. Wieder was dazugelernt. Einen kleinen Schönheitsfehler hat Deine Kürzung: Sie ist ein klein wenig zu rigoros. Wenn ich den Code 1:1 übernehme, dann schalten die Kanäle mit gleicher Start- und Endzeit durch - und das sollte nicht sein. Ich muss in diesem Fall beide Möglichkeiten von > und < 'explizit' definieren, damit = als Einzelfall 'implizit' eingeschlossen ist. (Mit Sprache kenne ich mich aus... Hätte ich die Logik so begriffen wie die Terminologie, hätte ich keine Sorgen. :)) So klappt's (getestet):

Code: Alles auswählen

def __contains__(self, time):                       
        if self.start < self.end:
            return self.start <= time < self.end
        elif self.start > self.end:
            return self.start < time or time < self.end
Bedeutet immer noch eine Reduktion des Codes um 50% bei gleicher Wirkung. "Faszinierend. (Zitat Mr. Spock)" Zur Zeit verrrichtet der kleine Code seinen Dienst zuverlässig und tut genau das, was er soll. Nun kann ich mich daran machen, eine Eingabemaske zu basteln, in welcher ich während der Laufzeit die Schaltzeiten anpassen, programmierte Zeiten anzeigen sowie manuell einen Kanal auf On oder Off schalten kann (Übersteuerung des Zeitvergleichs), in der Luxusvariante mit HW-Taste und kleinem LC-Display im Raspi-Gehäuse. Man gönnt sich ja sonst nix... :mrgreen:

Danke nochmals und Gruss, Oli
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@loba74: ich wiederspreche Dir gerne. Dein Code liefert bei gleicher Start- und End-Zeit für jede Zeit None zurück, meiner False.
Da None bei Bedingungen zu False evaluiert wird, stört das zwar nicht wirklich, unschön ist es trotzdem, zumal meine Funktion zusätzlich kürzer ist.

Hier die vollständigen Tests:

Code: Alles auswählen

>>> def contains(start, end, time):                                                                                                
...         if start <= end:                                                                                                       
...             return start <= time < end        # Zyklus innerhalb eines Tages                                                   
...         else:                                                                                                                  
...             # Zyklus mit Datumssprung: VOR Mitternacht, NACH Einschalten oder NACH Mitternacht, VOR Abschalten                 
...             return start <= time or time < end                                                                                 
...                                                                                                                                
>>> assert not contains(8,16,6)                                                                                                             
>>> assert contains(8,16,8)                                                                                                          
>>> assert contains(8,16,14)                                                                                                        
>>> assert not contains(8,16,16)                                                                                                       
>>> assert not contains(8,16,20)                                                                                                     
>>>
>>> assert not contains(12,12,4)
>>> assert not contains(12,12,12)
>>> assert not contains(12,12,16)
>>>
>>> assert contains(16,8,6)                                                                                                 
>>> assert not contains(16,8,8)                                                                                                
>>> assert not contains(16,8,14)                                                                                              
>>> assert contains(16,8,16)                                                                                             
>>> assert contains(16,8,20)

Code: Alles auswählen

>>> def contains(start, end, time):                                                                             
...         if start < end:                                                                                     
...             return start <= time < end        # Zyklus innerhalb eines Tages                                
...         elif start > end:
...             # Zyklus mit Datumssprung: VOR Mitternacht, NACH Einschalten oder NACH Mitternacht, VOR Abschalten
...             return start <= time or time < end
...
>>> assert not contains(8,16,6)                                                                                                             
>>> assert contains(8,16,8)                                                                                                          
>>> assert contains(8,16,14)                                                                                                        
>>> assert not contains(8,16,16)                                                                                                       
>>> assert not contains(8,16,20)                                                                                                     
>>>
>>> assert not contains(12,12,4)
>>> assert not contains(12,12,12)
>>> assert not contains(12,12,16)
>>>
>>> assert contains(16,8,6)                                                                                                 
>>> assert not contains(16,8,8)                                                                                                
>>> assert not contains(16,8,14)                                                                                              
>>> assert contains(16,8,16)                                                                                             
>>> assert contains(16,8,20)
Wie Du siehst, beide Lösungen verhalten sich richtig.
loba74
User
Beiträge: 5
Registriert: Freitag 24. Oktober 2014, 23:41

In der Theorie wage ich es nicht, Dir zu widersprechen. Wie gesagt, ich bin ein Noob und habe von der Theorie (noch) nicht viel Ahnung. Allerdings sieht die Praxis anders aus. Kanal 24, 25 und 27 haben Schaltzeiten von 00:00 bis 00:00, sollten also aus sein, sind aber dennoch an, wie mir NetBeans ausgibt (hier, morgens um sieben :)). Wie NetBeans meint, sind die Bedingungen für Kanal 24 bis 27 ebenfalls wahr und zieht an. Übrigens hat auch der Raspi die Relais im 'Feldtest' angezogen. Es scheint also nicht eine Sonderheit von NetBeans zu sein (die ganzen Prints habe ich nur eingefügt, damit die verschiedenen Schleifen was zu tun haben):
(17, 'reset')
06:45:29 Kanal 17 aus
(22, 'reset')
06:45:29 Kanal 22 an
(23, 'reset')
(24, 'reset')
06:45:29 Kanal 23 an
(25, 'reset')
06:45:29 Kanal 24 an
(27, 'reset')
06:45:29 Kanal 25 an
06:45:29 Kanal 27 an
17 False
22 True
23 True
24 True
25 True
27 True
17 True
06:47:00 Kanal 17 an
Gruss, Oli
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@loba74: dann ist wohl in einem anderen Teil des Programms etwas falsch.
juntiedt
User
Beiträge: 10
Registriert: Dienstag 13. Dezember 2016, 16:22

Hallo,

ich hab mir aus der Anleitung von BlackJack eine Zeitschaltuhr_class programmiert die als separater thread läuft um Regel_Loops mit z.B. einer Nachabsenkung zu steuern. Funktioniert sehr gut.

Jetzt möchte ich die Anregung von BlackJack mit erweiterten Zeitstrukturen zu arbeiten, umsetzen und stoße an meine Python Grenzen.
Wie macht man das am besten?

Code: Alles auswählen

#!/usr/bin/python

#import logging
import threading
import time
from datetime import datetime as DateTime, time as Time

# TODO Maybe configure basic logging here.
#LOG = logging.getLogger(__name__)

#:
#: Tuples of the form (name, (start, end)).
#:
# 
# TODO Maybe it is useful to extend this structure and the code by more
# than one time interval per relay.
# 

class Zeitschaltuhr(object):

    R1_Stat = None
    R2_Stat = None
    R3_Stat = None
    R4_Stat = None
    R5_Stat = None
    R6_Stat = None
    R7_Stat = None
    R8_Stat = None

    def __init__(self):
        self.RELAYS = [
        ('Relay 1', ('00:00:00', '00:00:00')),
        ('Relay 2', ('00:00:00', '00:00:00')),
        ('Relay 3', ('06:45:00', '22:00:00')),
        ('Relay 4', ('00:00:00', '00:00:00')),
        ('Relay 5', ('00:00:00', '00:00:00')),
        ('Relay 6', ('00:00:00', '00:00:00')),
        ('Relay 7', ('00:00:00', '00:00:00')),
        ('Relay 8', ('00:00:00', '00:00:00')),
        ]        
        print("init")
        self._lock = threading.Lock()
        self._zsu_thread = threading.Thread(target=self._run_uhr)
        self._zsu_thread.daemon = True  # Don't let this thread block exiting.
        self._zsu_thread.start()
        time.sleep(2)

#--------------------------------------        

    class Relay(object):

        def __init__(self, name):
            self.name = name
            self.state = None
            #print(self.name)   
    
        def switch(self, new_state):
            global R1_Stat
            global R2_Stat
            global R3_Stat
            global R4_Stat
            global R5_Stat
            global R6_Stat
            global R7_Stat
            global R8_Stat
            if self.state != new_state:
                self.state = new_state
                #print(self.name + " : " + str(self.state))
                if self.name == "Relay 1":
                    R1_Stat = self.state
                elif self.name == "Relay 2":
                    R2_Stat = self.state
                elif self.name == "Relay 3":
                    #print("R3")
                    R3_Stat = self.state
                    #print(R3_Stat)
                elif self.name == "Relay 4":
                    R4_Stat = self.state
                elif self.name == "Relay 5":
                    R5_Stat = self.state
                elif self.name == "Relay 6":
                    R6_Stat = self.state
                elif self.name == "Relay 7":
                    R7_Stat = self.state
                elif self.name == "Relay 8":
                    R8_Stat = self.state
                else:
                    print("Error: Relay unbekannt!")
    #            for name in self.name:
    #            print(self.name, ['aus', 'an'][new_state])
    #           if self.name == "Relay 1" and self.state == True:
    #               print("Relay1 do something")
                
    class TimeSpan(object):
        def __init__(self, start, end):
            if start > end:
                raise ValueError('start must not be greater than end')
            self.start = start
            self.end = end
        
        def __contains__(self, time):
            return self.start <= time < self.end
#------------------------------------------------
    def parse_time(self, string):
        return Time(*map(int, string.split(':')))
    
    def _run_uhr(self): 
        relays = [
            (self.Relay(name), self.TimeSpan(self.parse_time(start), self.parse_time(end)))
            for name, (start, end) in self.RELAYS
        ]
        #time.sleep(1)
        while True:
            now = DateTime.now().time()
            for relay, time_span in relays:
                relay.switch(now in time_span)
            time.sleep(1)

    def read_r1(self):
        with self._lock:
            return R1_Stat
    def read_r2(self):
        with self._lock:
            return R2_Stat        
    def read_r3(self):
        with self._lock:
            return R3_Stat
    def read_r4(self):
        with self._lock:
            return R4_Stat
    def read_r3(self):
        with self._lock:
            return R3_Stat
    def read_r5(self):
        with self._lock:
            return R5_Stat
    def read_r6(self):
        with self._lock:
            return R6_Stat
    def read_r7(self):
        with self._lock:
            return R7_Stat
    def read_r8(self):
        with self._lock:
            return R8_Stat        
#------------------------------------------------

ZSU = Zeitschaltuhr()

while True:
    if ZSU.read_r1() == True:
        print("Relay 1 An")
    elif ZSU.read_r1() == False:
        print("Relay 1 aus")
    if ZSU.read_r2() == True:
        print("Relay 2 An")
    elif ZSU.read_r2() == False:
        print("Relay 2 aus")    
    if ZSU.read_r3() == True:
        print("Relay 3 An")
    elif ZSU.read_r3() == False:
        print("Relay 3 aus")
    if ZSU.read_r4() == True:
        print("Relay 4 An")
    elif ZSU.read_r4() == False:
        print("Relay 4 aus")
    if ZSU.read_r5() == True:
        print("Relay 5 An")
    elif ZSU.read_r5() == False:
        print("Relay 5 aus")
    if ZSU.read_r6() == True:
        print("Relay 6 An")
    elif ZSU.read_r6() == False:
        print("Relay 6 aus")
    if ZSU.read_r7() == True:
        print("Relay 7 An")
    elif ZSU.read_r7() == False:
        print("Relay 7 aus")
    if ZSU.read_r8() == True:
        print("Relay 8 An")
    elif ZSU.read_r8() == False:
        print("Relay 8 aus")    
    else:
        print ("Relay unbekannt")
    time.sleep(10)


Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@juntiedt: globale Variablen und Klassenvariablen sind zwei verschiedene Dinge, die Du hier vermischst. Beides sollte man aber sowieso nicht verwenden. Wenn Du anfängst, Variablen durchzunummerieren, willst Du eigentlich eine passende Datenstruktur (hier ein Wörterbuch) verwenden. Auch großgeschriebene Attribute bedeuten in Python Konstanten, die dann aber nicht in __init__ definiert werden. Ein sleep in __init__ ist unerwartet, gehört also da nicht hin. Um Referenzen zurückzugeben, braucht man kein Lock. Entweder die Variable zeigt auf das eine Objekt oder ein anderes, einen Mischzustand kann es da nicht geben. Klassen innerhalb von Klassen zu definieren macht selten Sinn. Welchen Sinn macht die Relay-Klasse überhaupt? Die gehören auf Modulebene. Zum Parsen von Zeiten benutzt man auch Datetime.strptime: Datetime.strptime("13:02:44", "%H:%M:%S").time(). Funktionen durchzunummerieren ist genauso fragwürdige wie Variablennamen.

Edit:

Code: Alles auswählen

#!/usr/bin/python
import threading
import time
from datetime import datetime as DateTime
 
RELAYS = [
    ('Relay 1', ('00:00:00', '00:00:00')),
    ('Relay 2', ('00:00:00', '00:00:00')),
    ('Relay 3', ('06:45:00', '22:00:00')),
    ('Relay 4', ('00:00:00', '00:00:00')),
    ('Relay 5', ('00:00:00', '00:00:00')),
    ('Relay 6', ('00:00:00', '00:00:00')),
    ('Relay 7', ('00:00:00', '00:00:00')),
    ('Relay 8', ('00:00:00', '00:00:00')),
]

def parse_time(time):
    return DateTime.strptime(time, "%H:%M:%S").time()
        
class Zeitschaltuhr(object):
    def __init__(self, relays):
        self.relays = [
            (relay, parse_time(start), parse_time(end))
            for relay, (start, end) in relays
        ]
        self.relay_states = {}
        self.update()
        self._zsu_thread = threading.Thread(target=self._run_uhr)
        self._zsu_thread.daemon = True  # Don't let this thread block exiting.
        self._zsu_thread.start()
   
    def update(self):
        states = {}
        now = DateTime.now().time()
        for relay, start, end in self.relays:
            states[relay] = start <= now < end
        self.relay_states = states

    def _run_uhr(self):
        while True:
            self.update()
            time.sleep(1)
 
 
zeitschaltuhr = Zeitschaltuhr(RELAYS)
while True:
    for relay, state in sorted(zeitschaltuhr.relay_states.items()):
        print("{} {}".format(relay, "an" if state else "aus"))
    time.sleep(10)
juntiedt
User
Beiträge: 10
Registriert: Dienstag 13. Dezember 2016, 16:22

Danke!
das nenne ich genial verkürzt! Bin halt doch noch ein Anfänger in Python.

Wenn ich jetzt in einer Regelfuntion (endlos Schleife) z.B. Relay 3 zeitsteuern möchte :
while True:
if Relay 3 on .....
wie würde man das in Python richtig schreiben?

und noch eine Frage:
wenn man mehrere Schaltperioden pro Tag (z.B. 6) für ein Relay haben möchte, wie würde dann der Python code aussehen?
Antworten