Ankündigung

Einklappen
Keine Ankündigung bisher.

Datenbankeinträge beim Start Löschen oder Reorganisieren

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    Datenbankeinträge beim Start Löschen oder Reorganisieren

    Hallo,

    über welchen Weg würdet Ihr ein oder mehrere SQL-Statements beim shNG-Start auf die Datenbank 'abfeuern'? Z.B. eine eigene Logik mit eigenem Item dafür?

    Konkrete Aufgabenstellung:

    Ich habe diverse Meßwerte, bei denen beim Aus-/Wiedereinschalten von shNG der Wert '0' in die Datenbank geschrieben wird, obwohl dieser Wert schon rein logisch im Betrieb nicht vorkommen sollte (z.B. Heizungsvorlauf). Das führt zu äußerst unschönen Spikes in der Visu. Bei anderen Werten (z.B. Heizungsrücklauf auf der Fernwärme-Primärseite) spinnt manchmal der Sensor; neulich hatte ich für 2 Minuten 2.500°C Rücklauf, vermutlich direkt aus der Hölle, welche sich direkt unter unserem Haus befindet. Ergebnis war, dass durch den Auto-Zoom des Plots meine Wochenanzeige mit normal <80°C direkt auf der x-Achse verlief, also quasi nicht ablesbar war; und das wäre wegen der Wochenanzeige für die kommende 7 Tage so geblieben.

    Ich würde daher gern für solche Einträge ein "Clean script" schreiben, z.B. sowas hier:
    DELETE * FROM `log` WHERE `time` > 1571565600000 AND `item_id` IN (93,94,95,96) AND `val_num` = 0
    DELETE * FROM `log` WHERE `time` > 1571565600000 AND `item_id` = 97 AND `val_num` > 80

    Aber wie an die Sache herangehen? Oder bleibt die simpelste Methode, einfach den letzten Wert zu cachen (was andere unschöne Effekte verursacht, und mir bei 2500° Rücklauf auch nicht hilft)?

    Side note: Ja, ich muss den zeitweise defekten Sensor mal wechseln. Ja, ich bin mir bewusst, dass dadurch Trends / Berechnungen / kumulierte Werte verfälscht werden können; diese Diskussion gibt es ja immer wieder seit sh.py Zeiten. Aber das ist hier nicht das Thema.

    /tom
    Zuletzt geändert von Tom Bombadil; 09.11.2019, 15:58.

    #2
    Hast du mal probiert, die deleteLog und _slice_condition Methode entsprechend zu erweitern? Der Zeitfaktor wird dort ja abdeckt, jetzt bräuchte es noch einen Parameter "val_num", bei dem man Operator und Zahlenwert mit dem Aufruf der Methode mitschicken muss..?

    Kommentar


      #3
      Tom Bombadil

      Die Probleme habe ich auch.
      Bislang habe ich 2 Maßnahmen, um dem entgegenzuwirken.

      in shNG auf Itemebene:
      Hier nutze ich in den items, die nie NULL sein können, ein eval:
      eval: value if value != 0 else None
      Somit wird der Itemwert nie NULL und damit kommend diese Werte auch nicht in die DB

      DB Pflege
      Hierzu logge ich mich mit Putty ein und nutze einen mysql Befehl:
      DELETE FROM `log` WHERE `item_id` IN (54,57,58,59,60,61) AND `val_num` = 0
      Das geht auch im laufenden Betrieb.

      Ansonsten sollten wir uns wirklich mal anschauen, sql-Befehl über das Plugin zu schleusen.

      Beste Grüße

      Kommentar


        #4
        Tom Bombadil

        Hallo,
        ich habe heute mal etwas rumprobiert und folgendes entwickelt.

        Mit Hilfe der neuen UserFunctions (kommen im nächsten Release) kann man auch auf die DB zugreifen. Ich habe ein mysql bzw. mariadb und damit etwas rumprobiert.
        Damit lassen sich solche Dinge, wie von Dir im ersten Beitrag gezeigt, sehr leicht umsetzen.

        Hier mal ein Auszug aus meiner UserFunction mit dem Namen / Dateinamen "mysql":
        Code:
        #!/usr/bin/env python3
        # mysql.py
        
        from lib.item import Items
        import logging
        _logger = logging.getLogger(__name__)
        
        from datetime import datetime
        import time
        import pymysql.cursors
        
        _VERSION     = '0.1.0'
        _DESCRIPTION = 'Abfragefunktionen der mysql DB'
        
        
        def connect_db():
            connection = pymysql.connect(host='localhost',
                                         user='smarthome',
                                         password='smarthome',
                                         db='smarthome',
                                         charset='utf8mb4',
                                         cursorclass=pymysql.cursors.DictCursor)
            return connection
        
        def readItem(id):
            """
            :param id: Id of the item within the database
            :param cur: A database cursor object if available (optional)
        
            :return: Data for the selected item
            """
            COL_ITEM = ('id', 'name', 'time', 'val_str', 'val_num', 'val_bool', 'changed')
            columns = ", ".join(COL_ITEM)
            if type(id) == str:
                query = f"SELECT {columns} FROM item WHERE name = '{str(id)}'"
            else:
                query = f"SELECT {columns} FROM item WHERE id = {id}"
            return fetch_one(query)
        
        def get_item_id(item):
            """
            Returns the ID of the given item
        
            :param item: Item to get the ID for
            :param create: If True, the item is created within the database if it does not exist
        
            :return: id of the item within the database
            :rtype: int | None
            """
            _logger.warning(f"Die Userfunction 'get_item_id' wurde aufgerufen mit item: {item}")
            try:
                row = readItem(str(item))
            except Exception as e:
                _logger.warning(f"id(): No id found for item {item} - Exception {e}")
        
            if (row is None) or (0 >= len(row)) :
                return None
            _logger.warning(f"item_id is: {row['id']}")
            return int(row['id'])
        
        def fetch_all(item_id):
            _logger.warning("Die Userfunction 'fetch_all' wurde aufgerufen")
            result = []
            try:
                connection = connect_db()
                with connection.cursor() as cursor:
                    sql = "select * from log where (item_id=%s) AND (time = None OR 1 = 1)"
                    cursor.execute(sql, (item_id,))
                    result = cursor.fetchall()
            finally:
                connection.close()
            _logger.warning(f'mysql result: {result}')
            return result
        
        def fetch_one(query):
            _logger.warning(f"Die Userfunction 'fetch_one' wurde aufgerufen mit der Abfrage {query}")
            try:
                connection = connect_db()
                with connection.cursor() as cursor:
                    cursor.execute(query)
                    serie = cursor.fetchone()
            finally:
                connection.close()
            return serie
        
        def get_db_version():
            _logger.warning(f"Die Userfunction 'get_db_version' wurde aufgerufen")
            
            try:
                connection = connect_db()
                with connection.cursor() as cursor:
                    cursor.execute('SELECT VERSION()')
                    version = cursor.fetchone()
            finally:
                connection.close()
            _logger.warning(f'Database version: {version}')
            return list(version.values())[0]
        
        def execute_query(query):
            _logger.warning(f"Die Userfunction 'execute_query' wurde aufgerufen mit query {query}")
            try:
                connection = connect_db()
                with connection.cursor() as cursor:
                    cursor.execute(query)
                connection.commit()
            finally:
                connection.close()
        
        def fetch_query(query):
            _logger.warning(f"Die Userfunction 'query' wurde aufgerufen mit query {query}")
            try:
                connection = connect_db()
                with connection.cursor() as cursor:
                    cursor.execute(query)
                    result = cursor.fetchall()
            finally:
                connection.close()
            _logger.warning(f'mysql result: {result}')
            return result
        Darin sind ein paar Grundfunktionen. Für Deine Aufgabe von oben, könntest Du Dir nun folgendes Item anlegen:
        Code:
        database:
            maintenance:
                clean_log:
                    type: bool
                    eval: uf.mysql.execute_query('DELETE * FROM `log` WHERE `time` > 1571565600000 AND `item_id` IN (93,94,95,96) AND `val_num` = 0')
        Immer, wenn das Item getriggert wird, wird die UserFunction ausgelöst.

        Um zu sehen, was beim Löschen passieren würde, kannst Du die UserFunction fetch_query nutzen.
        Code:
        database:
            maintenance:
                clean_log:
                    type: bool
                    eval: uf.mysql.execute_query('DELETE * FROM `log` WHERE `time` > 1571565600000 AND `item_id` IN (93,94,95,96) AND `val_num` = 0')
                to_be_deleted_logs:
                    type: list
                    eval: uf.mysql.fetch_query('SELECT * FROM `log` WHERE `time` > 1571565600000 AND `item_id` IN (93,94,95,96) AND `val_num` = 0')
        Hier bekommst Du eine Liste mit den Logs zurück, die der Abfrage entsprechen.

        Sicher keine Gewähr. Aber Du kannst es gern mal testen.

        Michael

        Kommentar


          #5
          Tom Bombadil
          Hallo,
          konntest Du es schon mal ausprobieren?

          Kommentar


            #6
            Tut mir leid, Michael - bis jetzt noch nicht. Hab Anfang der Woche voller Schwung den Test des neuen Plot-Typs in Angriff genommen, den Wolfram gebaut hat. Dann flanschte plötzlich die Arbeit wieder dazwischen.

            Diesen Test muss ich ohnehin vorsichtig angehen - Löschaktionen auf einer DB mit den Daten von etlichen Jahren sind kein Spaß. Also Backups machen usw. Wird dauern ...

            Die Idee ist trotzdem Klasse!

            /tom

            Kommentar

            Lädt...
            X