Logging error - aber ich kann einfach nichts finden

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.
Antworten
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Nabend,
ich schreibe gerade ne Zeitschaltuhr mit html UI.
Dafür nutze ich den apscheduler, eine mysql Datenbank und websockets um Verbindung zum html UI herzustellen.

Aktuell bin ich gerade dabei das nach dem Neuanlegen eines Schaltplans das html UI per Websockets einen entsprechenden Befehl an das python Skript sendet.
So das selbiges den neuen Datensatz dann aus der mysql-Datenbank laden in im apscheduler speichern kann.

Das senden des entsprechenden Kommandos funktioniert auch, dann tauchen aber 2 große Probleme auf (und ich kann den verfluchten Fehler nicht finden):

1. Laut logging auf der Console wird der Befehl "load_one" 2x ausgeführt und ich verstehe einfach nicht warum. Ich suche und suche und finde nichts.
Über websockets kommt nur ein Befehl an, dann dürfte das Ganze auch nur 1 x ausgeführt werden.

2. Er bringt jedesmals diesen logging error.
Ich suche dann nach dem Fehler aber finde nichts. Noch besser, das Programm arbeitet trotz Fehler richtig und führt den neu angelegten Job trotzdem korrekt aus.

Ich verbringe jetzt schon ein paar Stunden mit der Suche nach dem Fehler, und so langsam verliere ich den Verstand.

Hauptprogramm

Code: Alles auswählen

import asyncio
from asyncio.queues import Queue
import logging
import json


import pymysql
import websockets
from lib_timerswitch import TimerSwitch

machine_ip = "192.168.127.30"
machine_port = 5555
consumers_gui = []


def send_to_usb(tmpname, tmpid, switch_to, loop):
    logger.info('Timerswitch ... USB            ID = %s  switch to = %s' % (tmpname, switch_to))
    if (switch_to is False) and (loop is False):
        timer.delete_db(tmpid)


def send_to_radio(tmpname, tmpid, switch_to, loop):
    logger.info('Timerswitch ... Funk           ID = %s  switch to = %s  loop = %s' % (tmpname, switch_to, loop))
    if switch_to == "False" and loop == "False":
        timer.delete_db(tmpid)


def send_to_relais_tf(tmpname, tmpid, switch_to, loop):
    logger.info('Timerswitch ... TF Relais      ID = %s  switch to = %s  loop = %s' % (tmpname, switch_to, loop))
    if switch_to == "False" and loop == "False":
        timer.delete_db(tmpid)


@asyncio.coroutine
def sending_loop_gui(websocket):
    # create sending-queue
    loop = asyncio.get_event_loop()
    sending_queue_gui = Queue()
    logger.info('websockets .... GUI Queue startet')

    def changed(tmp):
        loop.call_soon_threadsafe(sending_queue_gui.put_nowait, tmp)

    try:
        consumers_gui.append(changed)
        logger.info('websockets .... ein GUI-Client wurde in die Queue aufgenommen')

        while True:
            tmp_data = yield from sending_queue_gui.get()
            yield from websocket.send(tmp_data)
            logger.debug('websockets .... Sende json Daten -> GUI : %s' % tmp_data)

    finally:
        consumers_gui.remove(changed)
        logger.info('websockets .... ein GUI-Client wurde aus der Queue entfernt')


@asyncio.coroutine
def socket_handler_gui(websocket, path):
    # set up sending-queue
    task = asyncio.async(sending_loop_gui(websocket))

    while True:

        # get message from client
        message_rec = yield from websocket.recv()

        # leave if client is disconnect
        if message_rec is None:
            break

        # switch to different tasks
        logger.debug('websockets .... Empfange json Daten von GUI-Client : %s' % message_rec)
        tmp = gui_message_handler(message_rec)
        # if tmp is not False:
        # yield from websocket.send(tmp)

    # close sending-queue if client discconect
    task.cancel()


def gui_message_handler(_tmp):
    # decode JSON String
    _tmp_json = json.loads(_tmp)

    # extract variables from json
    json_usage = _tmp_json[0]
    json_device_id = _tmp_json[1]
    json_device_type = _tmp_json[2]
    json_data_name = _tmp_json[3]
    json_data_value = _tmp_json[4]
    logger.debug(
        'websockets .... GUI -> Nachricht %s von %s -> %s : %s = %s' % (
            json_usage, json_device_id, json_device_type, json_data_name, json_data_value))

    if json_usage == "timer_new":
        timer.load_one(json_data_value)
        logger.debug('AAAAA')
        return False
    elif json_usage == "timer_update":
        timer.reload(json_data_value)
        return False
    elif json_usage == "timer_delete":
        timer.delete_job(json_data_value)
        return False
    else:
        return False


def set_logging():
    console_handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S')
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)


if __name__ == '__main__':
    #
    # set up Logging Deamon
    #
    logger = logging.getLogger('Timerswitch')
    logger.setLevel(logging.DEBUG)
    set_logging()
    logger.info('Timerswitch ... startet')
    #
    # set up MySQL Connection
    #
    mysql_connection = pymysql.connect(host="192.168.168.99", port=1234, user="python", passwd="python",
                                       db="smartHome",
                                       autocommit=True)
    mysql_cursor = mysql_connection.cursor()
    logger.info('mySQL ......... Verbindung online')
    #
    # set up TimerSwitch
    #
    methods = {'USB-Steckdose': send_to_usb, 'Funkschalter': send_to_radio, 'TF Relais': send_to_relais_tf}
    timer = TimerSwitch(logger, mysql_cursor, methods)
    timer.start()
    #
    # set up WebSocktes
    #
    gui_server = websockets.serve(socket_handler_gui, machine_ip, machine_port)
    asyncio.get_event_loop().run_until_complete(gui_server)
    logger.info('websockets .... System online')
    asyncio.get_event_loop().run_forever()
lib_timerswitch

Code: Alles auswählen

import datetime

from apscheduler.schedulers.background import BackgroundScheduler


class TimerSwitch:
    _STANDARD_FUNCTION = "Funkschalter"

    def __init__(self, logging_daemon, db_connection, methods):
        self._scheduler = BackgroundScheduler()
        self._methods = methods
        self._logging_daemon = logging_daemon
        self._db_connection = db_connection
        self._logging_daemon.info('TimerSwitch ... initialisiert')

    def _load_all(self):
        self._db_connection.execute("SELECT * FROM schedulers")
        results = self._db_connection.fetchall()
        self._logging_daemon.info('Timerswitch ... Load All')
        for result in results:
            self._add(result)

    def load_one(self, record_id):
        self._db_connection.execute("SELECT * FROM schedulers WHERE id = %s" % record_id)
        result = self._db_connection.fetchone()
        while result is not None:
            self._logging_daemon.info('Timerswitch ... Load One %s' % record_id)
            self._add(result)

    def _add(self, dataset):
        # Daten einlesen
        record_id = dataset[0]
        record_title = dataset[1]
        record_function = dataset[2]
        record_date_start = dataset[3]
        record_time_start = dataset[4]
        record_time_stop = dataset[5]
        record_date_stop_check = dataset[6]
        record_date_stop = dataset[7]
        record_duration = dataset[8]
        record_intervall_number = dataset[9]
        record_intervall_unit = dataset[10]
        record_weekday_monday = dataset[11]
        record_weekday_tuesday = dataset[12]
        record_weekday_wednesday = dataset[13]
        record_weekday_thursday = dataset[14]
        record_weekday_friday = dataset[15]
        record_weekday_saturday = dataset[16]
        record_weekday_sunday = dataset[17]

        # ID's für den python scheduler erzeugen
        record_id_on = str(record_id) + 'on'
        record_id_off = str(record_id) + 'off'

        # falls keine Funktion angegeben wurde
        if record_function is None or record_function not in self._methods:
            record_function = self._STANDARD_FUNCTION

        # Zeiten erzeugen
        time_start_hour = int(record_time_start[0:record_time_start.find(":")])
        time_start_minute = int(record_time_start[record_time_start.find(":") + 1:])
        time_stop_hour = int(record_time_stop[0:record_time_stop.find(":")])
        time_stop_minute = int(record_time_stop[record_time_stop.find(":") + 1:])

        # Daten erzuegen
        date_start_year = record_date_start.year
        date_start_month = record_date_start.month
        date_start_day = record_date_start.day
        switch_start_on = datetime.datetime(date_start_year, date_start_month, date_start_day, time_start_hour,
                                            time_start_minute, 0)
        switch_start_off = datetime.datetime(date_start_year, date_start_month, date_start_day, time_stop_hour,
                                             time_stop_minute, 0)

        if (record_duration != 'einmalig') and record_date_stop_check:
            date_stop_year = record_date_stop.year
            date_stop_month = record_date_stop.month
            date_stop_day = record_date_stop.day
            switch_stop_on = datetime.datetime(date_stop_year, date_stop_month, date_stop_day, time_start_hour,
                                               time_start_minute, 0)
            switch_stop_off = datetime.datetime(date_stop_year, date_stop_month, date_stop_day, time_stop_hour,
                                                time_stop_hour, 0)
        else:
            switch_stop_on = None
            switch_stop_off = None

        if (time_stop_hour < time_start_hour) or (
                    (time_stop_hour == time_start_hour) and (time_stop_minute < time_start_minute)):
            switch_start_off = switch_start_off + datetime.timedelta(days=1)
            if switch_stop_off is not None:
                switch_stop_off = switch_stop_off + datetime.timedelta(days=1)

        self._logging_daemon.info('Timerswitch ... Add New Timer: Name = %s   Start = %s   Stop = %s' % (
            record_title, switch_start_on, switch_start_off))

        interval_minutes = 0
        interval_hours = 0
        interval_days = 0
        interval_weeks = 0
        if record_intervall_unit == "Minuten":
            interval_minutes = record_intervall_number
        elif record_intervall_unit == "Stunden":
            interval_hours = record_intervall_number
        elif record_intervall_unit == "Tage":
            interval_days = record_intervall_number
        elif record_intervall_unit == "Wochen":
            interval_weeks = record_intervall_number

        week = ""
        if record_weekday_monday:
            week = "mon"
        if record_weekday_tuesday:
            week = ",".join((week, "tue"))
        if record_weekday_wednesday:
            week = ",".join((week, "wed"))
        if record_weekday_thursday:
            week = ",".join((week, "thu"))
        if record_weekday_friday:
            week = ",".join((week, "fri"))
        if record_weekday_saturday:
            week = ",".join((week, "sat"))
        if record_weekday_sunday:
            week = ",".join((week, "sun"))

        if record_duration == 'einmalig':
            self._scheduler.add_job(self._methods[record_function], 'date', run_date=switch_start_on,
                                    args=[record_title, record_id, "True", "False"], id=record_id_on)
            self._scheduler.add_job(self._methods[record_function], 'date', run_date=switch_start_off,
                                    args=[record_title, record_id, "False", "False"], id=record_id_off)
        elif record_duration == 'intervall':
            self._scheduler.add_job(self._methods[record_function], 'interval', minutes=interval_minutes,
                                    hours=interval_hours, days=interval_days, weeks=interval_weeks,
                                    start_date=switch_start_on, end_date=switch_stop_on,
                                    args=[record_title, record_id, "True", "True"],
                                    id=record_id_on)
            self._scheduler.add_job(self._methods[record_function], 'interval', minutes=interval_minutes,
                                    hours=interval_hours, days=interval_days, weeks=interval_weeks,
                                    start_date=switch_start_off, end_date=switch_stop_off,
                                    args=[record_title, record_id, "False", "True"],
                                    id=record_id_off)
        elif record_duration == 'wochentag':
            self._scheduler.add_job(self._methods[record_function], 'cron', day_of_week=week, hour=time_start_hour,
                                    minute=time_start_minute, start_date=switch_start_on, end_date=switch_stop_on,
                                    args=[record_title, record_id, "True", "True"], id=record_id_on)
            self._scheduler.add_job(self._methods[record_function], 'cron', day_of_week=week, hour=time_stop_hour,
                                    minute=time_stop_minute, start_date=switch_start_off, end_date=switch_stop_off,
                                    args=[record_title, record_id, "False", "True"], id=record_id_off)

    def reload(self, record_id):
        self._scheduler.remove_job(record_id)
        self.load_one(record_id)
        self._logging_daemon.info('Timerswitch ... Reload       ID = %s' % record_id)

    def delete_job(self, record_id):
        self._scheduler.remove_job(record_id)
        self._logging_daemon.info('Timerswitch ... Delete Job   ID = %s' % record_id)

    def delete_db(self, record_id):
        self._db_connection.execute("DELETE FROM schedulers WHERE id = %s" % record_id)
        self._logging_daemon.info('Timerswitch ... Delete DB    ID = %s' % record_id)

    def start(self):
        self._load_all()
        self._scheduler.start()
Und hier, am wichtigsten, die Ausgabe auf der Console

Code: Alles auswählen

C:\Python34\python.exe D:/GoogleDrive/privat/Projekte/python/Ameisen/timerswitch.py
2015-08-10 00:37:40 : Timerswitch ... startet
2015-08-10 00:37:40 : mySQL ......... Verbindung online
2015-08-10 00:37:40 : TimerSwitch ... initialisiert
2015-08-10 00:37:40 : Timerswitch ... Load All
2015-08-10 00:37:40 : Timerswitch ... Add New Timer: Name = Pflanzenlicht   Start = 2015-08-07 08:00:00   Stop = 2015-08-07 21:00:00
2015-08-10 00:37:40 : Timerswitch ... Add New Timer: Name = Blinker   Start = 2015-08-09 21:30:00   Stop = 2015-08-09 21:31:00
2015-08-10 00:37:40 : Timerswitch ... Add New Timer: Name = qw   Start = 2015-08-21 00:35:00   Stop = 2015-08-21 00:35:00
2015-08-10 00:37:40 : websockets .... System online
2015-08-10 00:37:43 : websockets .... GUI Queue startet
2015-08-10 00:37:43 : websockets .... ein GUI-Client wurde in die Queue aufgenommen
2015-08-10 00:37:44 : websockets .... Empfange json Daten von GUI-Client : ["timer_new","gui","","id",63]
2015-08-10 00:37:44 : websockets .... GUI -> Nachricht timer_new von gui ->  : id = 63
2015-08-10 00:37:44 : Timerswitch ... Load One 63
2015-08-10 00:37:44 : Timerswitch ... Add New Timer: Name = qw   Start = 2015-08-21 00:35:00   Stop = 2015-08-21 00:35:00
--- Logging error ---
Traceback (most recent call last):
  File "C:\Python34\lib\site-packages\websockets\server.py", line 69, in handler
    yield from self.ws_handler(self, path)
  File "D:/GoogleDrive/privat/Projekte/python/Ameisen/timerswitch.py", line 74, in socket_handler_gui
    tmp = gui_message_handler(message_rec)
  File "D:/GoogleDrive/privat/Projekte/python/Ameisen/timerswitch.py", line 97, in gui_message_handler
    timer.load_one(json_data_value)
2015-08-10 00:38:23 : websockets .... GUI Queue startet
2015-08-10 00:38:23 : websockets .... ein GUI-Client wurde in die Queue aufgenommen
2015-08-10 00:38:54 : websockets .... ein GUI-Client wurde aus der Queue entfernt
2015-08-10 00:38:54 : websockets .... GUI Queue startet
2015-08-10 00:38:54 : websockets .... ein GUI-Client wurde in die Queue aufgenommen
2015-08-10 00:38:55 : websockets .... Empfange json Daten von GUI-Client : ["timer_new","gui","","id",64]
2015-08-10 00:38:55 : websockets .... GUI -> Nachricht timer_new von gui ->  : id = 64
2015-08-10 00:38:55 : Timerswitch ... Load One 64
2015-08-10 00:38:55 : Timerswitch ... Add New Timer: Name = TEEEEST   Start = 2015-08-10 00:40:00   Stop = 2015-08-10 00:41:00
2015-08-10 00:38:55 : Timerswitch ... Load One 64
2015-08-10 00:38:55 : Timerswitch ... Add New Timer: Name = TEEEEST   Start = 2015-08-10 00:40:00   Stop = 2015-08-10 00:41:00
--- Logging error ---
Traceback (most recent call last):
  File "C:\Python34\lib\site-packages\websockets\server.py", line 69, in handler
    yield from self.ws_handler(self, path)
  File "D:/GoogleDrive/privat/Projekte/python/Ameisen/timerswitch.py", line 74, in socket_handler_gui
    tmp = gui_message_handler(message_rec)
  File "D:/GoogleDrive/privat/Projekte/python/Ameisen/timerswitch.py", line 97, in gui_message_handler
    timer.load_one(json_data_value)
2015-08-10 00:40:00 : Timerswitch ... Funk           ID = TEEEEST  switch to = True  loop = True

Process finished with exit code -1
Bitte, könntet jemand nur mal 5-10 Minuten drüber schauen ... ich glaube ich sitze einfach irgendwie auf der Leitung ...
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@der_Mausbiber: Bei Deinem "logging-Error" sehe ich nur den halben Traceback, nicht aber die eigentliche Fehlermeldung, da scheinst Du den wichtigsten Teil zur Fehlersuche wegzuschmeißen. Versuch mal herauszufinden, warum das so ist.

Anmerkungen zum Code von oben nach unten:
Zeile 18: besser not switch_to and not loop
Zeile 24&30: warum ist "False" hier ein String?
Zeile 87ff: Was soll das json_-Präfix? Warum sendest Du kein Wörterbuch? Warum verwendest Du keinen fertigen Dispatcher? device_id, device_type und data_name werden gar nicht verwendet?

Warum heißt das Modul lib_timerswitch? Dass es ein Modul ist, sieht man doch schon daran, dass Du es importierst.
Zeile 17: *-Selects sind genauso schlimm wie *-Importe. Falls Du mal was an der Tabellenstruktur änderst, wirst Du schwer lokalisierbare Fehler produzieren.
Zeile 24: NEIN! Man formatiert sich keine SQL-Befehle zusammen, zudem wird record_id ungesehen von JSON übernommen, das kann also JEDER beliebige String sein. Lesen, verstehen, nie wieder vergessen
Zeile 26: warum while? result wird nicht geändert -> Endlosschleife bis eine Exception auftritt.
Zeile 32ff: Warum das record_-Präfix? Besser, das Dataset gleich in eine passende Struktur packen, noch besser ein ORM benutzen, dann erledigen sich die ganzen anderen Fehler auch gleich mit.
Zeile 57: Stillschweigend inkonsitente Daten zu bereinigen führt zu schwer lokalisierbaren Fehlern.
Zeile 60ff: Die Datenbank kennt soetwas wie Datetime. date_start scheint das ja schon zu sein, warum pflückst Du das auseinander um es dann wieder Zusammenzusetzen? Warum ist time_start ein String?
Zeile 108ff: join funktioniert anders.

Insgesamt: Überdenke Deine Datenstrukturen, nicht alles muß man in lokale Variablen speichern, keine Präfixe, benutze ein ORM.

Die Lösung Deines akuten Problems ist zwischen der Auflistung Deiner anderen Probleme oben mit enthalten. Wenn Du also alles verstanden hast, dann hast Du auch Deine Frage beantwortet.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

omg ... ihr macht mich fertig :)

Mir war ja schon klar, das das Programm nicht wirklich sauber geschrieben ist, aber es lief ja bis auf den besagten Fehler.
Hmm, natürlich werde ich heute abend versuchen die ganzen Fehler mit meinen beschränkten Fähigkeiten zu korriegieren - ich hoffe nur das ich das auch hinbekomme.
Das ein oder andere übersteigt derzeit doch meinen Horizont.

Auf ein paar Anmerkungen habe ich allerdings Antworten:
da scheinst Du den wichtigsten Teil zur Fehlersuche wegzuschmeißen. Versuch mal herauszufinden, warum das so ist.
Das habe ich gestern schon eine ganze Weile erfolglos versucht, leider.
Zeile 87ff: Was soll das json_-Präfix? Warum sendest Du kein Wörterbuch? Warum verwendest Du keinen fertigen Dispatcher? device_id, device_type und data_name werden gar nicht verwendet?
json-Präfix nutze ich aus rein optischen Gründen und weil ich dachte das so die Variablenamen aussagekräftiger sind. Deswegen nutze ich generell oft präfixe, denke einfach so ist es für andere leichter zu lesen.
Ein Wörterbuch sende ich nicht, weil so weit ich weiß, das Standart-Austausch-Format mit javascript Programmen doch json sein sollte - hier dachte ich das ich genau das richtige machen würde.
Ich habe es aber auch nicht hinbekommen mit einem javascript über websockets zu kommunizieren und dabei ein Wörterbuch-Format zu nutzen das auch javascript versteht.
Die Variablen die ich nicht nutze stammen noch aus einem anderen Programm, genauso wie der genau Aufbau meines json-Strings. Beides nutze ich bei anderen Programmteile, hier ist es unnötig.


Also erst einmal Danke, ist harter Tabak für mich.
BlackJack

@der_Mausbiber: So etwas mit JSON im Namen ist natürlich aussagekräftig. Es sagt aus das ein JSON-Wert an den Namen gebunden ist. Ist dann nur etwas verwirrend wenn der Wert mit JSON eigentlich gar nichts zu tun hat, sondern einfach nur irgendwann einmal aus einem JSON-Wert kam. Das hilft dem Leser aber nicht wirklich beim Verständnis.

Mit Wörterbuch senden war gemeint ein JSON-Objekt zu senden, was geparst in Python dann ein Wörterbuch wird. Das ist deutlich üblicher als ein JSON-Array zu senden wo die Positionen implizite Bedeutungen haben die man dann aber kennen muss. Ein JSON-Objekt wo die Werte Namen bekommen ist für den Leser leichter zu verstehen und lässt sich auch leichter um Werte erweitern weil nicht die Reihenfolge wichtig ist, sondern die Namen. Wie man es hinbekommt ein JSON-Array zu versenden aber kein JSON-Objekt verstehe ich nicht. Es sei denn Du verwendest gar kein JSON sondern versendest die normale Zeichenkettenrepräsentation von Listen und bist glücklicherweise noch in keinen Fall gelaufen wo das kein gültiges JSON war. Python und JSON sind sich da zwar teilweise sehr ähnlich, das ist aber nicht das gleiche! Man muss das schon explizit als JSON kodieren damit es robust funktioniert.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

so, ich konnte jetzt mal einen genaueren Blick auf die Fehler werfen.
Bei einigen bin ich weitergekommen, bei anderen verstehe ich noch nicht ganz was ihr wollt, bzw. wie es geht.
Zeile 18: besser not switch_to and not loop
klingt logisch und ist erledigt.
Zeile 24&30: warum ist "False" hier ein String?
Gute Frage, hatte ich wohl übersehen, jetzt ist es kein String mehr - erledigt.
Zeile 17: *-Selects sind genauso schlimm wie *-Importe. Falls Du mal was an der Tabellenstruktur änderst, wirst Du schwer lokalisierbare Fehler produzieren.
Das wusste ich nicht, ich habe jede Menge Bücher aus den letzten 20 Jahren in den SELECT Abfragen so genutzt werden.
Von daher ist mir die Aussage komplett neu, bin aber gerne bereit umzudenken.
Richtig wäre also etwas in der Art?

Code: Alles auswählen

SELECT id, title, function, date_start, .... FROM schedulers
Zeile 24: NEIN! Man formatiert sich keine SQL-Befehle zusammen, zudem wird record_id ungesehen von JSON übernommen, das kann also JEDER beliebige String sein. Lesen, verstehen, nie wieder vergessen
Also Teil 2 habe ich verstanden, löst folgende Variante das Problem?

Code: Alles auswählen

    def load_one(self, record_id):
        record_id = int(record_id)
        self._db_connection.execute("SELECT * FROM schedulers WHERE id = %s" % record_id)
Zum 1. Teil: Wenn ich mir den SQL-Befehl nicht zusammen formatiere, was dann? Strings addieren?
Zeile 26: warum while? result wird nicht geändert -> Endlosschleife bis eine Exception auftritt.
Jetzt wo du es fragst, frage ich es mich auch.
Tatsächlich habe ich das Ganze von hier http://dev.mysql.com/doc/connector-pyth ... chone.html
Ist aber wirklich ein dummer Fehler, habe es so gelöst, richtig?

Code: Alles auswählen

        result = self._db_connection.fetchone()
        if result is not None:
            self._logging_daemon.info('Timerswitch ... Load One %s' % record_id)
Zeile 32ff: Warum das record_-Präfix? Besser, das Dataset gleich in eine passende Struktur packen, noch besser ein ORM benutzen, dann erledigen sich die ganzen anderen Fehler auch gleich mit.
Gleich vorweg, da ich wirklich nur nebenbei versuche etwas python zu programmieren und auch beruflich kein Programmierer/Informatiker bin habe ich zwar irgendwann schonmal von ORM gehört, aber mehr auch nicht.
Das präfix sollte wie gesagt nur die Lesbarkeit steigern.
Und wie ich das Dataset in eine passende Struktur packe weiß ich ehrlich gesagt nicht.
Ich hätte lieber etwas in der Art genutzt

Code: Alles auswählen

ecord_function = dataset['function']
In anderen Sprachen klappt das, aber hier habe ich irgendwie nichts passendes gefunden.
Zeile 57: Stillschweigend inkonsitente Daten zu bereinigen führt zu schwer lokalisierbaren Fehlern.
Hmm, eigentlich war das nur als eine Art Fallback gedacht falls aus irgendeinem Grund die "Funktion" fehlt.
Der Abschnitt ist mittlerweile aber unnötig, da schon im UI dafür gesorgt wird das immer eine passende "Funktio"n eingegeben wird.
Habe den Abschnitt also komplett entfernt.
Zeile 60ff: Die Datenbank kennt soetwas wie Datetime. date_start scheint das ja schon zu sein, warum pflückst Du das auseinander um es dann wieder Zusammenzusetzen? Warum ist time_start ein String?
Liegt wie schon beschrieben am UI. Dort ist es leichter für mich Werte wie "22:15" oder "20.07.2015" zu benutzen.
Der date- und timepicker des UI geben mir auch diese Art von Werten zurück.
Bin aber am überlegen hier etwas zu ändern, dann würde das umformatieren entfallen.
Wird also noch erledigt.
Zeile 108ff: join funktioniert anders.
Verdammt, ich dachte wirklich so gehts.
Hmm, ist join dann hier überhaupt der richtige Weg oder sollte ich eine andere Art von String-Manipulation nutzen um den Ausdruck zusammen zu setzen?
nsgesamt: Überdenke Deine Datenstrukturen, nicht alles muß man in lokale Variablen speichern, keine Präfixe, benutze ein ORM.
Das mit dem ORM ist so ein wenig ein Problem. Zum einen befürchte ich das mich das überfordert und zum anderen das ich dadurch noch mehr schlechten Code produziere.
Will mich aber auf jeden Fall damit beschäftigen.

@BlackJack
Hmm, wenn ich ehrlich bin war mein erster Gedanke auch ein python Wörterbuch in json verpackt zum javascript zu schicken.
Ich habe es aber auch nach x-Versuchen nicht geschafft dieses Wörterbuch von der javascript Seite her auszulesen.
Irgendwann habe ich dann aufgegeben.
Ansonsten kodiere ich doch meine Liste in json, was fehlt den noch damit es immer "gültig" ist?
Leider verstehe ich noch nicht komplett was du mir sagen willst, ich werde mich aber nochmal informieren und dann beim zweiten Lesen hoffentlich mehr verstehen.

Ansonsten schon einmal vielen Dank euch Zwei :)
Insgesamt war ja doch nicht alles schlecht was programmiert habe.
BlackJack

@der_Mausbiber: Das Umwandeln in `int` löst das Problem zwar, aber das funktioniert nicht für alles. Was für alle Werte, unabhängig vom Typ funktioniert ist die Werte vom Datenbankmodul in die Anfrage einsetzen zu lassen. Ob man die SQL-Anweisung nun selber als Zeichenkette ”formatiert” oder ”addiert” ist egal, das löst doch beides das Problem nicht. Die `execute()`-Methode kennt ein zweites Argument und Platzhalter für Werte im ersten Argument. Die Platzhalter sind in gewissen Grenzen leider vom Datenbankmodul abhängig, da müsstest Du in dessen Dokumentation schauen. Oder ein ORM verwenden was einem diese Unterschiede zwischen Datenbankmodulen ausbügelt.

Wenn Du Deine Liste als JSON kodierst fehlt nichts. Ich hatte nur die Befürchtung Du würdest einfach `str()` auf der Liste aufrufen und das senden.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Wenn Du Deine Liste als JSON kodierst fehlt nichts. Ich hatte nur die Befürchtung Du würdest einfach `str()` auf der Liste aufrufen und das senden.
Nein, von der javascript Seite her nutze ich

Code: Alles auswählen

JSON.stringify(["timer_new","gui","","id",timer])
Und das sende ich dann.
Sollte also richtig sein.
Ein Wörterbuch wäre mir aber lieber gewesen.

Ansonsten schaue ich mir die execute-Methode nochmal an, ich würde ungern für diese wirklich einfachen Datenbankabfragen einen ganzen Framework dazuladen.
Zumindest wenn es sich irgendwie verhindern lässt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Was hindert Dich daran:

Code: Alles auswählen

JSON.stringify({usage: "timer_new", timer: timer})
zu schreiben?
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Was hindert Dich daran: ... zu schreiben?
Auf den ersten Blick nichts.
Das Programm ist aber nur ein Teilstück eines größeren und da habe ich mir ne Art "Austauschformat" mit 5 Feldern zusammengezimmert.
In der Regel werden auch alle 5 Felder genutzt, in diesem Beispiel aber nicht.

Natürlich könnte ich das für diesen Teil ändern?
Aber mal ehrlich, hätte das irgendwelche Auswirkungen auf das Programm - mal abgesehen von der Optik?

Wenn ich dich um einen Gefallen bitten dürfte, dann sag mir doch bitte ob ich mit meinen Antworten und Änderungen oben schon einmal soweit richtig liege?
An den noch offenen Baustellen arbeite ich heute.
BlackJack

@der_Mausbiber: Das ist definitiv mehr als nur Optik wenn man statt einer Liste mit fester Länge wo die einzelnen Elemente eine bestimmte Bedeutung haben, ein Objekt (in JavaScript-Terminologie) verwendet, wo die Elemente zwar keine Reihenfolge, dafür aber einen beschreibenden Namen haben. Der Leser bekommt bei den Daten schon eine Idee was die einzelnen Elemente bedeuten, man kann einfach Elemente weglassen ohne dafür einen Platzhalter-Wert einfügen zu müssen (und dabei einen speziellen Wert dafür zu ”verschwenden”) und man kann auch neue Elemente hinzufügen, die bereits bestehender Code ganz einfach ignoriert.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

So, ich habe jetzt mal die Datenbank etwas überarbeitet, Änderungen am UI vorgenommen und versucht so viel von euren Hinweisen zu beachten wie mir möglich war.
Folgendes ist dabei herausgekommen:

scheduler.py

Code: Alles auswählen

import asyncio
from asyncio.queues import Queue
import logging
import json


import pymysql
import websockets
from timerswitch import TimerSwitch

machine_ip = "192.168.1.15"
machine_port = 5555
consumers_gui = []


def send_to_usb(tmpname, tmpid, switch_to, loop):
    logger.info('Timerswitch ... USB            ID = %s  switch to = %s  loop = %s' % (tmpname, switch_to, loop))
    if (not switch_to) and (not loop):
        timer.delete_db(tmpid)


def send_to_radio(tmpname, tmpid, switch_to, loop):
    logger.info('Timerswitch ... Funk           ID = %s  switch to = %s  loop = %s' % (tmpname, switch_to, loop))
    if (not switch_to) and (not loop):
        timer.delete_db(tmpid)


def send_to_relais_tf(tmpname, tmpid, switch_to, loop):
    logger.info('Timerswitch ... TF Relais      ID = %s  switch to = %s  loop = %s' % (tmpname, switch_to, loop))
    if (not switch_to) and (not loop):
        timer.delete_db(tmpid)


@asyncio.coroutine
def sending_loop_gui(websocket):
    # create sending-queue
    loop = asyncio.get_event_loop()
    sending_queue_gui = Queue()
    logger.info('websockets .... GUI Queue startet')

    def changed(tmp):
        loop.call_soon_threadsafe(sending_queue_gui.put_nowait, tmp)

    try:
        consumers_gui.append(changed)
        logger.info('websockets .... ein GUI-Client wurde in die Queue aufgenommen')

        while True:
            tmp_data = yield from sending_queue_gui.get()
            yield from websocket.send(tmp_data)
            logger.debug('websockets .... Sende json Daten -> GUI : %s' % tmp_data)

    finally:
        consumers_gui.remove(changed)
        logger.info('websockets .... ein GUI-Client wurde aus der Queue entfernt')


@asyncio.coroutine
def socket_handler_gui(websocket, path):
    # set up sending-queue
    task = asyncio.async(sending_loop_gui(websocket))

    while True:

        # get message from client
        message_rec = yield from websocket.recv()

        # leave if client is disconnect
        if message_rec is None:
            break

        # switch to different tasks
        logger.debug('websockets .... Empfange json Daten von GUI-Client : %s' % message_rec)
        gui_message_handler(message_rec)

    # close sending-queue if client discconect
    task.cancel()


def gui_message_handler(_tmp):
    # decode JSON String
    _tmp = json.loads(_tmp)

    # extract variables from json
    command = _tmp[0]
    scheduler_id = _tmp[1]
    logger.debug(
        'websockets .... GUI -> Nachricht: %s ID =  %s' % (command, scheduler_id))

    if command == "new":
        timer.load_one(scheduler_id)
    elif command == "update":
        timer.reload(scheduler_id)
    elif command == "delete":
        timer.delete_job(scheduler_id)


def set_logging():
    console_handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S')
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)


if __name__ == '__main__':
    #
    # set up Logging Deamon
    #
    logger = logging.getLogger('Timerswitch')
    logger.setLevel(logging.DEBUG)
    set_logging()
    logger.info('Timerswitch ... startet')
    #
    # set up MySQL Connection
    #
    mysql_connection = pymysql.connect(host="192.168.1.15", port=3306, user="user", passwd="pass",
                                       db="smartHome",
                                       autocommit=True)
    mysql_cursor = mysql_connection.cursor()
    logger.info('mySQL ......... Verbindung online')
    #
    # set up TimerSwitch
    #
    methods = {'USB-Steckdose': send_to_usb, 'Funkschalter': send_to_radio, 'TF Relais': send_to_relais_tf}
    timer = TimerSwitch(logger, mysql_cursor, methods)
    timer.start()
    #
    # set up WebSocktes
    #
    gui_server = websockets.serve(socket_handler_gui, machine_ip, machine_port)
    asyncio.get_event_loop().run_until_complete(gui_server)
    logger.info('websockets .... System online')
    asyncio.get_event_loop().run_forever()
timerswitch.py

Code: Alles auswählen

from apscheduler.schedulers.asyncio import AsyncIOScheduler


class TimerSwitch:
    _STANDARD_FUNCTION = "Funkschalter"

    def __init__(self, logging_daemon, db_connection, methods):
        self._scheduler = AsyncIOScheduler()
        self._methods = methods
        self._logging_daemon = logging_daemon
        self._db_connection = db_connection
        self._logging_daemon.info('TimerSwitch ... initialisiert')

    def _load_all(self):
        sql = "SELECT scheduler_id, title, device, date_start_on, date_start_off, date_stop, date_stop_on, " \
              "date_stop_off, duration, interval_number, interval_unit, weekly_monday, weekly_tuesday, " \
              "weekly_wednesday, weekly_thursday, weekly_friday, weekly_saturday, weekly_sunday FROM schedulers;"
        self._db_connection.execute(sql)
        results = self._db_connection.fetchall()
        self._logging_daemon.info('Timerswitch ... Load All')
        for result in results:
            self._add(result)

    def load_one(self, scheduler_id):
        scheduler_id = int(scheduler_id)
        sql = "SELECT scheduler_id, title, device, date_start_on, date_start_off, date_stop, date_stop_on, " \
              "date_stop_off, duration, interval_number, interval_unit, weekly_monday, weekly_tuesday, " \
              "weekly_wednesday, weekly_thursday, weekly_friday, weekly_saturday, weekly_sunday FROM schedulers " \
              "WHERE scheduler_id = %s;"
        self._db_connection.execute(sql, scheduler_id)
        result = self._db_connection.fetchone()
        if result is not None:
            self._logging_daemon.info('Timerswitch ... Load One %s' % scheduler_id)
            self._add(result)

    def _add(self, dataset):
        # Daten einlesen
        scheduler_id = dataset[0]
        title = dataset[1]
        device = dataset[2]
        date_start_on = dataset[3]
        date_start_off = dataset[4]
        date_stop_on = dataset[6]
        date_stop_off = dataset[7]
        duration = dataset[8]
        interval_number = dataset[9]
        interval_unit = dataset[10]
        weekly_monday = dataset[11]
        weekly_tuesday = dataset[12]
        weekly_wednesday = dataset[13]
        weekly_thursday = dataset[14]
        weekly_friday = dataset[15]
        weekly_saturday = dataset[16]
        weekly_sunday = dataset[17]

        # ID's f�r den python scheduler erzeugen
        scheduler_id_on = str(scheduler_id) + 'on'
        scheduler_id_off = str(scheduler_id) + 'off'

        self._logging_daemon.info('Timerswitch ... Add New Timer: Name = %s   Device = %s Start = %s   Stop = %s' % (
            title, self._methods[device], date_start_on, date_start_off))

        interval_minutes = 0
        interval_hours = 0
        interval_days = 0
        interval_weeks = 0
        if interval_unit == "Minuten":
            interval_minutes = interval_number
        elif interval_unit == "Stunden":
            interval_hours = interval_number
        elif interval_unit == "Tage":
            interval_days = interval_number
        elif interval_unit == "Wochen":
            interval_weeks = interval_number

        week = ""
        if weekly_monday:
            week = "mon"
        if weekly_tuesday:
            week += ",tue"
        if weekly_wednesday:
            week += ",wed"
        if weekly_thursday:
            week += ",thu"
        if weekly_friday:
            week += ",fri"
        if weekly_saturday:
            week += ",sat"
        if weekly_sunday:
            week += ",sun"

        if duration == 'einmalig':
            self._scheduler.add_job(self._methods[device], 'date', run_date=date_start_on,
                                    args=[title, scheduler_id, True, False], id=scheduler_id_on)
            self._scheduler.add_job(self._methods[device], 'date', run_date=date_start_off,
                                    args=[title, scheduler_id, False, False], id=scheduler_id_off)
        elif duration == 'intervall':
            self._scheduler.add_job(self._methods[device], 'interval', minutes=interval_minutes,
                                    hours=interval_hours, days=interval_days, weeks=interval_weeks,
                                    start_date=date_start_on, end_date=date_stop_on,
                                    args=[title, scheduler_id, True, True],
                                    id=scheduler_id_on)
            self._scheduler.add_job(self._methods[device], 'interval', minutes=interval_minutes,
                                    hours=interval_hours, days=interval_days, weeks=interval_weeks,
                                    start_date=date_start_off, end_date=date_stop_off,
                                    args=[title, scheduler_id, False, True],
                                    id=scheduler_id_off)
        elif duration == 'wochentag':
            self._scheduler.add_job(self._methods[device], 'cron', day_of_week=week, hour=date_start_on.hour,
                                    minute=date_start_on.minute, start_date=date_start_on, end_date=date_stop_on,
                                    args=[title, scheduler_id, True, True], id=scheduler_id_on)
            self._scheduler.add_job(self._methods[device], 'cron', day_of_week=week, hour=date_start_off.hour,
                                    minute=date_start_off.minute, start_date=date_start_off, end_date=date_stop_off,
                                    args=[title, scheduler_id, False, True], id=scheduler_id_off)

    def reload(self, scheduler_id):
        self.delete_job(scheduler_id)
        self.load_one(scheduler_id)
        self._logging_daemon.info('Timerswitch ... Reload       ID = %s' % scheduler_id)

    def delete_job(self, scheduler_id):
        self._scheduler.remove_job(str(scheduler_id) + 'on')
        self._scheduler.remove_job(str(scheduler_id) + 'off')
        self._logging_daemon.info('Timerswitch ... Delete Job   ID = %s' % scheduler_id)

    def delete_db(self, scheduler_id):
        scheduler_id = int(scheduler_id)
        self._db_connection.execute("DELETE FROM schedulers WHERE scheduler_id = %s", scheduler_id)
        self._logging_daemon.info('Timerswitch ... Delete DB    ID = %s' % scheduler_id)

    def start(self):
        self._load_all()
        self._scheduler.start()
Ich glaube das meiste habe ich geändert, einschließlich dem json-Aufruf.
Es wäre super wenn jemand zu den geänderten Skripten seine Meinung abgeben könnte, vor allem wenn irgendwo noch derbe Fehler stecken.

Das einzige was ich noch nicht verstanden habe ist
Besser, das Dataset gleich in eine passende Struktur packen, noch besser ein ORM benutzen
Was genau meinst du mit passende Struktur? In ein Wörterbuch packen?
Und in wie fern ein ORM nutzen? Sowas wie SQLAlchemy? Ist das nicht etwas zu überladen für die paar Abfragen?

Könntest du mir den Punkt bitte etwas genauer erklären?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@der_Mausbiber: so wie ich das sehe, sendest Du immer noch JSON-Listen. Was soll der Unterstrich bei _tmp und warum heißt die Message _tmp? Abkürzung für time-message-protocol? Benutze am besten keine Abkürzungen,, vor allem nicht solche, bei denen man nicht gleich versteht, was sie bedeuten. Soweit ich das weiß kennt pymysql einen Dictionary-Cursor. Oder Du benutzt namedtuples. Was ist der Unterschied zwischen _load_one und _load_all? Wie kann man die beiden Funktionen zu einer zusammenfassen? Wo hast Du noch unnötige Code-Wiederholungen, die sich immer nur leicht ändern?
Die Intervall-Zeilen ließen sich z.B. so zusammenfassen:

Code: Alles auswählen

UNIT2KEYWORD = {'Minuten': 'minutes', 'Stunden': 'hours', 'Tage': 'days', 'Wochen': 'weeks'}

interval_argument = {UNIT2KEYWORD[interval_unit]: interval_number}
Bei week weißt Du nicht, wie man join richtig benutzt und hast das ganze auf Zeichenkettenverkettung umgeschrieben.

ORMs vereinfachen die Bedienung schon am einer Tabelle.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

sendest Du immer noch JSON-Listen
Ich muss doch auch ein Objekt übergeben mit dem javascript etwas anfangen kann.
Mit einem Wörterbuch kam ich da nicht weiter.
Was soll der Unterstrich bei _tmp und warum heißt die Message _tmp?
Unterstrich steht für private Variable, bin mir ziemlich sicher das irgendwo im Zusammenhang mit python gelesen zu haben.
Und "tmp" im allgemeinen halt für "temporär" ... hmm, auch die Abkürzung sehe ich in geschätzten 20-30% aller Code-Beispiele, quer durch alle Sprachen.

Ich versuche halt so wenig Variablen wie möglich anzulegen und nutze deshalb stellvertretend "tmp".
Kommt wohl noch aus der Zeit mit BASIC und einem Schneider CPC ...
Soweit ich das weiß kennt pymysql einen Dictionary-Cursor.
Danach habe ich gestern den ganzen Abend gesucht, aber nichts gefunden.
Wusste nur nicht die genaue Bezeichnung.
Jetzt weiß ich sie und werde nochmal suchen.
Unterschied zwischen _load_one und _load_all?
Naja, sagt doch schon der Name, das eine lädt alle, das andere nur einen bestimmten Datensatz.
Ich werde schauen ob ich beide zusammen gefasst bekomme.
Wo hast Du noch unnötige Code-Wiederholungen, die sich immer nur leicht ändern?
Die Intervall-Zeilen ließen sich z.B. so zusammenfassen:
Ich bin wie gesagt ein Gelegenheitsprogrammierer, und wäre froh wenn mir solche Abkürzungen einfallen würden.
Aber ich werde den Code nochmal durchsuchen und schauen was ich mit meinen Fähigkeiten noch hinbekomme.
Bei week weißt Du nicht, wie man join richtig benutzt und hast das ganze auf Zeichenkettenverkettung umgeschrieben.
Nicht nur, auch weil ich mir dachte das es so richtig ist.
Würde mir join also einen Vorteil bringen? Dann schaue ich mir auch das nochmal an.
ORMs vereinfachen die Bedienung schon am einer Tabelle.
Also ich habe mir mal kurz sqlAlchemy angeschaut und ganz ehrlich, für einen Gelegenheitsprogrammierer ist das ganz schön schwerer Stoff.
Ich verstehe es nicht.

So, ich habe dann wieder neue Arbeit, danke :)
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Eine Frage habe ich noch:

Welcher scheduler ist eigentlich der richtige?

Derzeit nutze ich den "BackgroundScheduler" vom apscheduler.
In den Dokus wird aber noch der "AsyncIOScheduler" erwähnt, welchen man nutzen solle, wenn man das AsyncIO-Modul benutzt.

Für die Verbindung über Websockets nutze ich aber dieses Modul, muss ich also auch den entsprechenden scheduler nutzen?

In der Doku werden die Vor- und Nachteile der einzelnen scheduler leider nicht so gut erklärt.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

so, habe jetzt alles geändert, allerdings bleiben 2-3 Fragen noch offen.
Bei week weißt Du nicht, wie man join richtig benutzt und hast das ganze auf Zeichenkettenverkettung umgeschrieben.
Nicht ganz, ich habe es so gelöst

Code: Alles auswählen

        week = ""
        if weekly_monday:
            week = "mon"
        if weekly_tuesday:
            week += ",tue"
        if weekly_wednesday:
            week += ",wed"
        if weekly_thursday:
            week += ",thu"
        if weekly_friday:
            week += ",fri"
        if weekly_saturday:
            week += ",sat"
        if weekly_sunday:
            week += ",sun"

        print(week)
mit "join" müsste es dann doch so aussehen

Code: Alles auswählen

        week = []
        if weekly_monday:
            week.append("mon")
        if weekly_tuesday:
            week.append("tue")
        if weekly_wednesday:
            week.append("wed")
        if weekly_thursday:
            week.append("thu")
        if weekly_friday:
            week.append("fri")
        if weekly_saturday:
            weeks.append("sat")
        if weekly_sunday:
            week.append("sun")

        week = ",".join(week)
        print(week)
Wenn man die "join"-Variante verkürzen könnte, dann okay, aber so sehe ich jetzt keinen Vorteil.
Und eine Verkürzung bekomme ich nicht hin.


Dein Code

Code: Alles auswählen

UNIT2KEYWORD = {'Minuten': 'minutes', 'Stunden': 'hours', 'Tage': 'days', 'Wochen': 'weeks'}
 
interval_argument = {UNIT2KEYWORD[interval_unit]: interval_number}
verkürzt das Ganze zwar, aber ich bekomme das nicht eingesetzt.
Zum einen erwartet der apscheduler die Argumente mit einem"=" und zum anderen ich den Begriff "minutes=" doch nicht einfach durch eine Variable ersetzen.
Zumindest habe ich es nicht hinbekommen den Code so einzubauen das ich ihn mit scheduler.add_job nutzen kann - da kommen mir wieder große Zweifel an mir?

Wo hast Du noch unnötige Code-Wiederholungen, die sich immer nur leicht ändern?
Da fallen mir schon noch ein paar Stellen ein, aber mir fällt nicht ein wie ich das ändern könnte.

und so sieht der Code jetzt aus

Code: Alles auswählen

import asyncio
from asyncio.queues import Queue
import logging
import json
from datetime import datetime

import pymysql
import websockets

from timerswitch import TimerSwitch

machine_ip = "192.168.1.1"
machine_port = 5555
consumers_gui = []


def send_to_usb(title, scheduler_id, switch_to, date_stop):
    logger.info(
        'Timerswitch ... USB            ID = %s  switch to = %s  date_stop = %s' % (title, switch_to, date_stop))
    check_date_stop(scheduler_id, switch_to, date_stop)


def send_to_radio(title, scheduler_id, switch_to, date_stop):
    logger.info('Timerswitch ... Funk           ID = %s  switch to = %s  loop = %s' % (title, switch_to, date_stop))
    check_date_stop(scheduler_id, switch_to, date_stop)


def send_to_relais_tf(title, scheduler_id, switch_to, date_stop):
    logger.info('Timerswitch ... TF Relais      ID = %s  switch to = %s  loop = %s' % (title, switch_to, date_stop))
    check_date_stop(scheduler_id, switch_to, date_stop)


def check_date_stop(scheduler_id, switch_to, date_stop):
    if (not switch_to) and (date_stop is not None):
        if datetime.now() >= date_stop:
            timer.delete_db(scheduler_id)


@asyncio.coroutine
def sending_loop_gui(websocket):
    # create sending-queue
    loop = asyncio.get_event_loop()
    sending_queue_gui = Queue()
    logger.info('websockets .... GUI Queue startet')

    def changed(tmp):
        loop.call_soon_threadsafe(sending_queue_gui.put_nowait, tmp)

    try:
        consumers_gui.append(changed)
        logger.info('websockets .... ein GUI-Client wurde in die Queue aufgenommen')

        while True:
            data = yield from sending_queue_gui.get()
            yield from websocket.send(data)
            logger.debug('websockets .... Sende json Daten -> GUI : %s' % data)

    finally:
        consumers_gui.remove(changed)
        logger.info('websockets .... ein GUI-Client wurde aus der Queue entfernt')


@asyncio.coroutine
def socket_handler_gui(websocket, path):
    # set up sending-queue
    task = asyncio.async(sending_loop_gui(websocket))

    while True:
        message = yield from websocket.recv()

        if message is None:
            break

        logger.debug('websockets .... Empfange json Daten von GUI-Client : %s' % message)
        gui_message_handler(message)

    task.cancel()


def gui_message_handler(message):
    # decode JSON String
    message = json.loads(message)

    # extract variables from json
    command = message[0]
    scheduler_id = message[1]
    logger.debug(
        'websockets .... GUI -> Nachricht: %s ID =  %s' % (command, scheduler_id))

    if command == "new":
        timer.load(scheduler_id)
    elif command == "update":
        timer.reload(scheduler_id)
    elif command == "delete":
        timer.delete_job(scheduler_id)


def set_logging():
    console_handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S')
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)


if __name__ == '__main__':
    #
    # set up Logging Deamon
    #
    logger = logging.getLogger('Timerswitch')
    logger.setLevel(logging.DEBUG)
    set_logging()
    logger.info('Timerswitch ... startet')
    #
    # set up MySQL Connection
    #
    mysql_connection = pymysql.connect(host="192.168.1.1", port=3306, user="user", passwd="pass",
                                       db="smartHome",
                                       autocommit=True)
    mysql_cursor = mysql_connection.cursor(pymysql.cursors.DictCursor)
    logger.info('mySQL ......... Verbindung online')
    #
    # set up TimerSwitch
    #
    methods = {'USB-Steckdose': send_to_usb, 'Funkschalter': send_to_radio, 'TF Relais': send_to_relais_tf}
    timer = TimerSwitch(logger, mysql_cursor, methods)
    timer.start()
    #
    # set up WebSocktes
    #
    gui_server = websockets.serve(socket_handler_gui, machine_ip, machine_port)
    asyncio.get_event_loop().run_until_complete(gui_server)
    logger.info('websockets .... System online')
    asyncio.get_event_loop().run_forever()

Code: Alles auswählen

from apscheduler.schedulers.asyncio import AsyncIOScheduler


class TimerSwitch:
    _STANDARD_FUNCTION = "Funkschalter"

    def __init__(self, logging_daemon, db_connection, methods):
        self._scheduler = AsyncIOScheduler()
        self._methods = methods
        self._logging_daemon = logging_daemon
        self._db_connection = db_connection
        self._logging_daemon.info('TimerSwitch ... initialisiert')

    def load(self, scheduler_id=None):
        sql = "SELECT scheduler_id, title, device, date_start_on, date_start_off, date_stop, date_stop_on, " \
              "date_stop_off, duration, interval_number, interval_unit, weekly_monday, weekly_tuesday, " \
              "weekly_wednesday, weekly_thursday, weekly_friday, weekly_saturday, weekly_sunday FROM schedulers"
        if isinstance(scheduler_id, int):
            sql += " WHERE scheduler_id = %s"
            self._db_connection.execute(sql, scheduler_id)
        else:
            self._db_connection.execute(sql)
        results = self._db_connection.fetchall()
        for result in results:
            self._add(result)

    def _add(self, dataset):
        # Daten einlesen
        scheduler_id = dataset['scheduler_id']
        title = dataset['title']
        device = dataset['device']
        date_start_on = dataset['date_start_on']
        date_start_off = dataset['date_start_off']
        date_stop_on = dataset['date_stop_on']
        date_stop_off = dataset['date_stop_off']
        duration = dataset['duration']
        interval_number = dataset['interval_number']
        interval_unit = dataset['interval_unit']
        weekly_monday = dataset['weekly_monday']
        weekly_tuesday = dataset['weekly_tuesday']
        weekly_wednesday = dataset['weekly_wednesday']
        weekly_thursday = dataset['weekly_thursday']
        weekly_friday = dataset['weekly_friday']
        weekly_saturday = dataset['weekly_saturday']
        weekly_sunday = dataset['weekly_sunday']

        # ID's für den python scheduler erzeugen
        scheduler_id_on = str(scheduler_id) + 'on'
        scheduler_id_off = str(scheduler_id) + 'off'

        self._logging_daemon.info('Timerswitch ... Add New Timer: Name = %s   Device = %s Start = %s   Stop = %s' % (
            title, self._methods[device], date_start_on, date_start_off))

        interval_minutes = 0
        interval_hours = 0
        interval_days = 0
        interval_weeks = 0
        if interval_unit == "Minuten":
            interval_minutes = interval_number
        elif interval_unit == "Stunden":
            interval_hours = interval_number
        elif interval_unit == "Tage":
            interval_days = interval_number
        elif interval_unit == "Wochen":
            interval_weeks = interval_number

        week = ""
        if weekly_monday:
            week = "mon"
        if weekly_tuesday:
            week += ",tue"
        if weekly_wednesday:
            week += ",wed"
        if weekly_thursday:
            week += ",thu"
        if weekly_friday:
            week += ",fri"
        if weekly_saturday:
            week += ",sat"
        if weekly_sunday:
            week += ",sun"

        if duration == 'einmalig':
            self._scheduler.add_job(self._methods[device], 'date', run_date=date_start_on,
                                    args=[title, scheduler_id, True, None], id=scheduler_id_on)
            self._scheduler.add_job(self._methods[device], 'date', run_date=date_start_off,
                                    args=[title, scheduler_id, False, None], id=scheduler_id_off)
        elif duration == 'intervall':
            self._scheduler.add_job(self._methods[device], 'interval', minutes=interval_minutes,
                                    hours=interval_hours, days=interval_days, weeks=interval_weeks,
                                    start_date=date_start_on, end_date=date_stop_on,
                                    args=[title, scheduler_id, True, None],
                                    id=scheduler_id_on)
            self._scheduler.add_job(self._methods[device], 'interval', minutes=interval_minutes,
                                    hours=interval_hours, days=interval_days, weeks=interval_weeks,
                                    start_date=date_start_off, end_date=date_stop_off,
                                    args=[title, scheduler_id, False, date_stop_off],
                                    id=scheduler_id_off)
        elif duration == 'wochentag':
            self._scheduler.add_job(self._methods[device], 'cron', day_of_week=week, hour=date_start_on.hour,
                                    minute=date_start_on.minute, start_date=date_start_on, end_date=date_stop_on,
                                    args=[title, scheduler_id, True, None], id=scheduler_id_on)
            self._scheduler.add_job(self._methods[device], 'cron', day_of_week=week, hour=date_start_off.hour,
                                    minute=date_start_off.minute, start_date=date_start_off, end_date=date_stop_off,
                                    args=[title, scheduler_id, False, date_stop_off], id=scheduler_id_off)

    def reload(self, scheduler_id):
        self.delete_job(scheduler_id)
        self.load(scheduler_id)
        self._logging_daemon.info('Timerswitch ... Reload       ID = %s' % scheduler_id)

    def delete_job(self, scheduler_id):
        self._scheduler.remove_job(str(scheduler_id) + 'on')
        self._scheduler.remove_job(str(scheduler_id) + 'off')
        self._logging_daemon.info('Timerswitch ... Delete Job   ID = %s' % scheduler_id)

    def delete_db(self, scheduler_id):
        if isinstance(scheduler_id, int):
            scheduler_id = int(scheduler_id)
            self._db_connection.execute("DELETE FROM schedulers WHERE scheduler_id = %s", scheduler_id)
            self._logging_daemon.info('Timerswitch ... Delete DB    ID = %s' % scheduler_id)

    def start(self):
        self.load()
        self._scheduler.start()
BlackJack

@der_Mausbiber: Der `week`-Code ist fehlerhaft. Ausser wenn Montags dabei ist, fängt die Zeichenkette mit einem Komma an, was wohl falsch sein dürfte.

Man könnte das so schreiben (ungetestet):

Code: Alles auswählen

week = ','.join(
    day for day, flag in zip(
        ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
        [monday, tuesday, wednesday, thursday, friday, saturday, sunday]
    ) if flag
)
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

So zum Beispiel:

Code: Alles auswählen

    UNIT2KEYWORD = {
        'Minuten': 'minutes',
        'Stunden': 'hours',
        'Tage': 'days',
        'Wochen': 'weeks'
    }
    WEEK2WEEK = [
        ('mon', 'weekly_monday'), 
        ('tue', 'weekly_tuesday'),
        ('wed', 'weekly_wednesday'),
        ('thu', 'weekly_thursday'),
        ('fri', 'weekly_friday'),
        ('sat', 'weekly_saturday'),
        ('sun', 'weekly_sunday'),
    ]


    def _add(self, dataset):
        title = dataset['title']
        date_start_on = dataset['date_start_on']
        date_start_off = dataset['date_start_off']
        date_stop_on = dataset['date_stop_on']
        date_stop_off = dataset['date_stop_off']
        
        method = self._methods[dataset['device']]

        self._logging_daemon.info('Timerswitch ... Add New Timer: Name = %s   Device = %s Start = %s   Stop = %s' % (
            title, method, date_start_on, date_start_off))

        week = ','.join(
            abr for abr, full in self.WEEK2WEEK
            if dataset[full]
        )

        duration = dataset['duration']
        if duration == 'einmalig':
            type = 'date'
            args_on = dict(run_date=date_start_on)
            args_off = dict(run_date=date_start_off)
        elif duration == 'intervall':
            type = 'intervall'
            interval_argument = {self.UNIT2KEYWORD[dataset['interval_unit']]: dataset['interval_number']}
            args_on = dict(interval_argument,
                start_date=date_start_on, end_date=date_stop_on
            )
            args_off = dict(interval_argument,
                start_date=date_start_off, end_date=date_stop_off
            )
        elif duration == 'wochentag':
            type = 'cron'
            args_on = dict(
                day_of_week=week, hour=date_start_on.hour,
                minute=date_start_on.minute,
                start_date=date_start_on,
                end_date=date_stop_on
            )
            args_off = dict(
                day_of_week=week, hour=date_start_off.hour,
                minute=date_start_off.minute,
                start_date=date_start_off,
                end_date=date_stop_off
            )

        scheduler_id = dataset['scheduler_id']
        self._scheduler.add_job(method, type, 
                                args=[title, scheduler_id, True, None],
                                id='%son' % scheduler_id, **args_on)
        self._scheduler.add_job(method, type,
                                args=[title, scheduler_id, False, date_stop_off],
                                id='%soff' % scheduler_id, **args_off)
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

WOW :D

Ihr habt es drauf, ich bin wirklich beeindruckt.

Von euch kann ich ehrlich mehr lernen als von zig-Seiten.

Einfach nur wow und Danke
Antworten