Skip to content

Skripte

mdzio edited this page Aug 27, 2022 · 28 revisions

Skripte

Der CCU-Historian besitzt eine Skriptumgebung, die direkten Zugriff auf die Datenbank besitzt. Dadurch eröffnen sich vielfältige Anwendungen: Automatisierte Massenkonfiguration, Erstellung von Statistiken und Analysen, Manipulation von Zeitreihen, usw.. Skripte können über WerkzeugeSkriptumgebung eingegeben und ausgeführt werden. Weitere Informationen sind im Handbuch Abschnitt Skriptumgebung zu finden. In den folgenden Abschnitten sind Beispielskripte aufgeführt. In der Regel besitzen die Skripte am Anfang einen Konfigurationsabschnitt, der vor Ausführung des Skripts sorgfältig bearbeitet werden sollte.

Automatische Ausführung von Skripten

Skripte können auch zyklisch automatisch ausgeführt werden (ab V3.2.0).

Die unten aufgeführten Skripte können mit einer kleineren Anpassung in der Konfigurationsdatei ccu-historian.config eingefügt werden. Das Kommando println muss durch log.info ersetzen.

Massenkonfiguration der Delta- und Swinging-Door-Kompression

Mit dem folgenden Skript wird die Delta- und Swinging-Door-Kompression automatisch für alle Datenpunkt gesetzt. Gerade die Swinging-Door-Kompression sollte aber noch manuell nachjustiert werden, da bei vielen Datenpunkten eine Abweichung viel größer als 0,01 erlaubt sein sollte.

/*
Autokonfiguration Deltakompression V1.0.0
Wichtig: Vor Anwendung des Skripts ein Backup der Datenbank erstellen!
*/

// *** Konfiguration ***

// Testlauf durchführen? Bei einem Testlauf wird die Datenbank nicht verändert.
// (Ja: true, Nein: false)
def testRun=true

// Sollen bereits konfigurierte Vorverarbeitungen überschrieben werden? 
// (Ja: true, Nein: false)
def overrideAll=true

// Sollen auch nicht geänderte Datenpunkte aufgelistet werden?
// (Ja: true, Nein: false)
def logNotChanged=false

// *** Skript ***
println "*** Autokonfiguration Deltakompression V1.0.0 ***"
println "Testlauf: ${testRun?"Ja":"Nein"}"
println "Alle überschreiben: ${overrideAll?"Ja":"Nein"}"
println "Alle auflisten: ${logNotChanged?"Ja":"Nein"}"

database.dataPoints.each { dp ->
  
  def currentCompr=getPreprocType(dp)
  def currentParam=getPreprocParam(dp)
  if (currentCompr!=PreprocType.DISABLED && !overrideAll) {
    println "\n$dp.displayName"
    println "  Vorhandene Vorverarbeitung wird nicht überschrieben: $currentCompr, $currentParam"
    return
  }
  
  def type=dp.attributes.type
  def ident=dp.id.identifier
  def compr=currentCompr
  def param=currentParam
  
  if (type=="ACTION") {
    compr=PreprocType.DISABLED
    param=0.0
	
  } else if (type in ["BOOL", "INTEGER", "ENUM", "ALARM"]) {
    compr=PreprocType.DELTA_COMPR
    param=0.1  
	
  } else if (type=="FLOAT") {
    compr=PreprocType.SWD_COMPR
    param=0.01  
	
  } else if (type=="STRING") {
    compr=PreprocType.DELTA_COMPR
    param=0.0
  }
  
  if (currentCompr!=compr || currentParam!=param) {
    println "\n$dp.displayName"
    println "  Vorverarbeitung wird abgeändert: $compr, $param"
    if (!testRun) {
      dp.attributes.preprocType=compr.ordinal()
      dp.attributes.preprocParam=(compr==PreprocType.DISABLED ? null : param)
      database.updateDataPoint(dp)
    }
  } else {
    if (logNotChanged) {
      println "\n$dp.displayName"
      println "  Vorverarbeitung muss nicht geändert werden: $compr, $param"
    }
  }
}

def getPreprocType(dp) {
  int idx=(dp.attributes.preprocType as Integer)?:PreprocType.DISABLED.ordinal()
  if (idx<0 || idx>=PreprocType.values().length) {
    throw new Exception("Invalid preprocessing type in database: "+idx)
  }
  PreprocType.values()[idx]
}

def getPreprocParam(dp) {
  (dp.attributes.preprocParam as Double)?:0.0
}

Massenkonfiguration der zu versteckenen Datenpunkte

/*
Autokonfiguration Versteckt V1.0.0
Wichtig: Vor Anwendung des Skripts ein Backup der Datenbank erstellen!
*/

// *** Konfiguration ***

// Testlauf durchführen? Bei einem Testlauf wird die Datenbank nicht verändert.
// (Ja: true, Nein: false)
def testRun=true

// Sollen bereits verstecke Datenpunkte sichtbar gemacht werden?
// (Ja: true, Nein: false)
def unhide=false

// Folgende Datenpunkte sollen versteckt werden:
def toHide=[
  "WORKING",
  "STICKY_UNREACH",
  "UNREACH",
  "ERROR",
  "INSTALL_TEST",
  "CONFIG_PENDING",
  "LOWBAT",
  "BOOT",
  "ERROR_SABOTAGE",
  "STICKY_SABOTAGE",
  "DIRECTION",
  "ERROR_CODE",
  "LOW_BAT",
  "OPERATING_VOLTAGE",
  "OPERATING_VOLTAGE_STATUS",
  "RSSI_DEVICE",
  "UPDATE_PENDING",
  "ACTIVITY_STATE",
  "LEVEL_STATUS",
  "PROCESS",
  "SECTION",
  "WEEK_PROGRAM_CHANNEL_LOCKS",
  "ACTUAL_TEMPERATURE_STATUS",
  "ERROR_OVERHEAT",
  "RSSI_PEER",
  "ERROR_NON_FLAT_POSITIONING",
  "HUMIDITY_STATUS",
  "ILLUMINATION_STATUS",
  "RAIN_COUNTER_OVERFLOW",
  "RAIN_COUNTER_STATUS",
  "SUNSHINEDURATION_OVERFLOW",
  "SUNSHINE_THRESHOLD_OVERRUN",
  "WIND_SPEED_STATUS",
  "WIND_THRESHOLD_OVERRUN",
  "ERROR_WIND_COMMUNICATION",
  "ERROR_WIND_NORTH",
  "TEMPERATURE_OUT_OF_RANGE",
  "DUTY_CYCLE",  
] as Set

// *** Skript ***
println "*** Autokonfiguration Versteckt V1.0.0 ***"
println "Testlauf: ${testRun?"Ja":"Nein"}"
println "Versteckte bei Bedarf wieder sichtbar machen: ${unhide?"Ja":"Nein"}"

database.dataPoints.each { dp ->
  def currentlyHidden=dp.historyHidden
  if (currentlyHidden && !unhide) {
    return
  }
  def wantedHidden=(dp.id.identifier in toHide)
  if (currentlyHidden!=wantedHidden) {
    println "\n$dp.displayName"
    println "  Änderung auf: ${wantedHidden?"Versteckt":"Sichtbar"}"
    if (!testRun) {
      dp.historyHidden=wantedHidden
      database.updateDataPoint(dp)
    }
  }
}

Inaktive Datenpunkte verstecken

/*
Inaktive Datenpunkte verstecken V1.0.0
Wichtig: Vor Anwendung des Skripts ein Backup der Datenbank erstellen!
*/

// *** Konfiguration ***

// Testlauf durchführen? Bei einem Testlauf wird die Datenbank nicht verändert.
// (Ja: true, Nein: false)
def testRun=true

// Sollen bereits verstecke Datenpunkte sichtbar gemacht werden?
// (Ja: true, Nein: false)
def unhide=false


// *** Skript ***
println "*** Inaktive Datenpunkte verstecken V1.0.0 ***"
println "Testlauf: ${testRun?"Ja":"Nein"}"
println "Versteckte bei Bedarf wieder sichtbar machen: ${unhide?"Ja":"Nein"}"

database.dataPoints.each { dp ->
  def currentlyHidden=dp.historyHidden
  if (currentlyHidden && !unhide) {
    return
  }
  
  // Verstecke den Datenpunkt, wenn er nicht aufgezeichnet wird
  def wantedHidden=dp.historyDisabled
  
  if (currentlyHidden!=wantedHidden) {
    println "\n$dp.displayName"
    println "  Änderung auf: ${wantedHidden?"Versteckt":"Sichtbar"}"
    if (!testRun) {
      dp.historyHidden=wantedHidden
      database.updateDataPoint(dp)
    }
  }
}

Versteckte Datenpunkte deaktivieren

/*
Versteckte Datenpunkte deaktivieren V1.0.0
Wichtig: Vor Anwendung des Skripts ein Backup der Datenbank erstellen!
*/

// *** Konfiguration ***

// Testlauf durchführen? Bei einem Testlauf wird die Datenbank nicht verändert.
// (Ja: true, Nein: false)
def testRun=true

// Sollen bereits deaktiverte Datenpunkte wieder aktiviert werden, wenn sie nicht versteckt sind?
// (Ja: true, Nein: false)
def activate=false


// *** Skript ***
println "*** Versteckte Datenpunkte deaktivieren V1.0.0 ***"
println "Testlauf: ${testRun?"Ja":"Nein"}"
println "Inaktive bei Bedarf wieder aktiv schalten: ${activate?"Ja":"Nein"}"

database.dataPoints.each { dp ->
  def currentlyDisabled=dp.historyDisabled
  if (currentlyDisabled && !activate) {
    return
  }
  
  // Deaktiviere den Datenpunkt, wenn er versteckt ist
  def wantedDisabled=dp.historyHidden
  
  if (currentlyDisabled!=wantedDisabled) {
    println "\n$dp.displayName"
    println "  Änderung auf: ${wantedDisabled?"Inaktiv":"Aktiv"}"
    if (!testRun) {
      dp.historyDisabled=wantedDisabled
      database.updateDataPoint(dp)
    }
  }
}

Alle Historien leeren

Achtung: Durch dieses Skript werden Daten gelöscht!

/*
Alle Historien leeren V1.0.0
Wichtig: Vor Anwendung des Skripts ein Backup der Datenbank erstellen!
*/

println "Historien werden geleert:"
def totalCnt=0
database.dataPoints.each { dp ->
    def cnt=database.deleteTimeSeries(dp, null, null)
    totalCnt+=cnt
    println "$dp.displayName: $cnt Einträge"
}
println "Gesamtanzahl der gelöschten Einträge: $totalCnt"

Damit auch die Datenbankdatei verkleinert wird, muss die Datenbank kompaktiert werden. Dies erfolgt mit der Kommandozeilenoption -compact (s.a. Handbuch Abschnitt Startparameter).

Alte Zeitreihendaten löschen

Achtung: Durch dieses Skript werden Daten gelöscht!

/*
Alte Zeitreihendaten löschen V1.0.0
Wichtig: Vor Anwendung des Skripts ein Backup der Datenbank erstellen!
*/

// *** Konfiguration ***

// Anzahl der Tage, die in der Datenbank verbleiben sollen.
def daysToKeep = 365

// Testlauf durchführen? Bei einem Testlauf wird die Datenbank nicht verändert.
// (Ja: true, Nein: false)
def testRun = true

// *** Skript ***

println "Alte Zeitreihendaten werden gelöscht:"
def deleteDate=new Date()-daysToKeep
println "Löschdatum: ${deleteDate.format("dd.MM.YYYY")}"
def totalCnt=0
database.dataPoints.each { dp ->
    def cnt
    if (testRun) {
        cnt=database.getCount(dp, null, deleteDate)
    } else {
        cnt=database.deleteTimeSeries(dp, null, deleteDate)
    }
    totalCnt+=cnt
    println "$dp.displayName: $cnt Einträge"
}
println "Gesamtanzahl der gelöschten Einträge: $totalCnt"

Damit auch die Datenbankdatei verkleinert wird, muss die Datenbank kompaktiert werden. Dies erfolgt mit der Kommandozeilenoption -compact (s.a. Handbuch Abschnitt Startparameter).

Histrorien von inaktiven Datenpunkten löschen

Falls Datenpunkte nachträglich auf Inaktiv in der Datenpunktkonfiguration gesetzt werden, haben sich unter Umständen schon etliche Einträge in den zugehörigen Tabellen angesammelt. Diese können mit dem folgenden Skript gelöscht werden:

/*
Historien inaktiver Datenpuntke leeren V1.0.0
Wichtig: Vor Anwendung des Skripts ein Backup der Datenbank erstellen!
*/

// *** Konfiguration ***

// Testlauf durchführen? Bei einem Testlauf wird die Datenbank nicht verändert.
// (Ja: true, Nein: false)
def testRun = true

// *** Skript ***

def totalCnt=0
println "Historien inaktiver Datenpunkte werden gelöscht:"
println "Testlauf: ${testRun?"Ja":"Nein"}"

database.dataPoints.each { dp ->
  if (!dp.historyDisabled) {
    return
  }
  def cnt
  if (testRun) {
     cnt = database.getCount(dp, null, null)
  } else {
     cnt = database.deleteTimeSeries(dp, null, null)
  }
  totalCnt += cnt
  println "$dp.displayName: $cnt Einträge gelöscht"
}
println "Gesamtanzahl der gelöschten Einträge: $totalCnt"

Damit auch die Datenbankdatei verkleinert wird, muss die Datenbank kompaktiert werden. Dies erfolgt mit der Kommandozeilenoption -compact (s.a. Handbuch Abschnitt Startparameter).

Statistiken von einem Datenpunkt über einen Zeitbereich berechnen

Mit diesem Skript wird der korrekte Mittelwert berechnet, auch wenn die Delta-Kompression für einen Datenpunkt aktiviert wurde. Durch die Delta-Kompression ist der Abstand zwischen den Messwerten nicht mehr konstant!

// Datenpunkt mit der ID 736 holen (s.a. Datenpunktliste letzte Spalte oder Datenpunktdetails)
def dp=database.getDataPoint(736)
println "Datenpunkt: $dp.displayName"

def end=new Date() // aktueller Zeitpunkt
def duration=24*60*60*1000 // ein Tag zurück in Millisekunden
def begin=new Date(end.time-duration) 
println "Beginn: $begin, Ende: $end"

// Zeitreihe holen
def ts=database.getTimeSeries(dp, begin, end)

// Statistik berechnen
def min=Double.POSITIVE_INFINITY
def max=Double.NEGATIVE_INFINITY
def integr=0
def previous
ts.each { pv ->
  if (pv.value<min) min=pv.value
  if (pv.value>max) max=pv.value
  if (previous!=null) {
    // Teilintegral berechnen: Messwert*Millisekunden
    integr+=previous.value*(pv.timestamp.time-previous.timestamp.time)
  }
  previous=pv
}
// Durchnitt ist Integral/Zeitbereichslänge in Millisekunden.
def avg=integr/duration

println "Einträge: $ts.size, Einträge pro Stunde: ${ts.size/duration*60*60*1000}"
println "Minimum: $min, Maximum: $max, Integral: $integr, Durchschnitt: $avg"

Beispielausgabe:

Datenpunkt: Wetterdaten.ACTUAL_TEMPERATURE
Beginn: Wed May 20 17:56:11 CEST 2020, Ende: Thu May 21 17:56:11 CEST 2020
Einträge: 380, Einträge pro Stunde: 15.8331600000
Minimum: 9.8, Maximum: 29.9, Integral: 1.6324556046000009E9, Durchschnitt: 18.89416209027779

Rangliste der Anzahl der Einträge in einem Zeitbereich

Für alle Datenpunkte wird die Anzahl der Einträge in der Datenbank für den angegebenen Zeitbereich ermittelt. Die Datenpunkte werden dann beginnend mit der höchsten Anzahl an Einträgen aufgelistet.

// Rangliste der Anzahl der Einträge in einem Zeitbereich, V1.0

// *** Konfiguration ***

// Beginn des Zeitbereichs
def begin=Date.parse('yyyy-MM-dd hh:mm', '2021-03-29 00:00')
// Ende des Zeitbereichs
def end=Date.parse('yyyy-MM-dd hh:mm', '2021-03-29 22:00')

// *** Skript ***

def dpCount =[]
database.dataPoints.each { dp ->
    def cnt=database.getCount(dp, begin, end)
    dpCount << [cnt, dp.displayName]
}
println " ANZAHL DATENPUNKT"
dpCount.sort { -it[0] }.take(100).each {
    println it[0].toString().padLeft(7) + " " + it[1]
}
Clone this wiki locally