Datenauswertung der Windkraftanlage Egneos Wind1

Webseite: http://egneos.de/portfoli/projekt-neos-wind-1/

Lage: http://www.openstreetmap.org/node/3996237479#map=15/51.3463/13.3807

Typ: Enercon E-101 http://www.enercon.de/produkte/ep-3/e-101/

  • 135m Mast
  • 101m Rotordurchmesser
  • 3.05 MW
  • Bauart: getriebelos, variable Drehzahl, Einzelblattverstellung
  • Baujahr 2015

Dokumentation und Quelltext: https://github.com/kolossos/energy-tests/blob/master/Dokumente/energy/PyOPC-visualizeSQLite3.ipynb

Motivation für die Datenauswertung:

Man besitzt nur das, was man aufschrauben und wo man reinschauen kann.

  • Mehr als nur Freude an einer ggf. kommenden Rendite
  • Begeisterung an faszinierender Technik (3MW sind beeindruckend. Kein Sportwagen mit solch einer Leistung.)
  • Die Anlage ist eine nette "kleine" Wetterstation
  • (Anlagenmonitoring) . Das macht aktiv der Hersteller.
  • Interesse an Datenanalyse (insbesonders der physikalischen Zusammenhänge.)
  • Interesse an Industriesteuerungen
  • Analyse des detailierten Zeitverlaufs
  • Die Energiewende verstehen und unterstützen
  • Hoffnung ggf. Begeisterung bei anderen zu wecken

Vorgehensweise

  • über VPN Zugriff auf die Anlage
  • Kommunikation mit dem Scada-System über OPC XML-DA mittels PyOPC
  • Genutzt wird Python, Ipython-Notebook, Pandas
  • Alles in eine Datenbank (SQLite) schreiben
  • Datenvisualisierung über Matplotlib

Gespeichert werden:

  • Anlagedaten

    • Windgeschwindigkeit und Windrichtung
    • Rotordrehzahl und Pitchwinkel
    • Leistung
    • Stromzähler
    • Betriebstunden
    • Blindstrom
  • Temperaturdaten

    • 25 Sensoren in der Gondel
    • 14 Sensoren in den Elektroschränken unten im Mast
  • Verfügbarkeitsdaten

Historische Daten werden aggregiert als Min, Max und Mittelwert gespeichert. Alle Datensätze mit Zeitstempel (UTC).

Neben der Angabe von Live-Daten, speichert die Anlage Daten für eine jeweils bestimmte Zeit:

  • RAW-Daten minütlich für die letzten 12 Stunden
  • REP-Daten aller 10 Minuten für die letzten 2 Tage
  • DAY-Daten täglich für 60 Tage
  • Week-Daten wöchentlich für 54 Wochen
  • Month-Daten monatlich für 36 Monate
  • Year-Daten jährlich für 10 Jahre
In [1]:
#!/usr/bin/env python
%matplotlib inline

#from PyOPC.OPCContainers import *
#from PyOPC.XDAClient import XDAClient

import time
import numpy as np
import matplotlib.pyplot as plt
In [2]:
import pandas as pd
from sqlalchemy import create_engine # database connection
import datetime as dt
from IPython.display import display
from IPython.display import clear_output

import scipy.stats

#http://pyopc.sourceforge.net/
#https://plot.ly/python/big-data-analytics-with-pandas-and-sqlite/

Daten der letzten 12 Stunden

In [3]:
disk_engine = create_engine('sqlite:///egneos2.db')
In [4]:
df = pd.read_sql_query('SELECT *'
                       ' FROM Wecstd_Raw limit 720 offset (select count(*) FROM Wecstd_Raw)-720'
                       , disk_engine)
df.index=pd.DatetimeIndex(df["index"])
df.drop('index', axis=1, inplace=True)
pd.read_sql_query('SELECT Count(*) '
                       'FROM Wecstd_Raw '
                       , disk_engine)
Out[4]:
Count(*)
0 3825921
In [5]:
df.head()
Out[5]:
Source Values MitVwind MaxVwind MinVwind MitNRotor MaxNRotor MinNRotor MitP MaxP ... PavaExtern Pitch ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8
index
2024-03-02 04:52:00 Raw 1.0 3.5 4.0 3.0 6.58 6.66 6.39 101.0 109.0 ... 113.0 1.0 65.535003 65.535003 65.535003 17.8 18.7 17.6 0.0 65535.0
2024-03-02 04:53:00 Raw 1.0 3.2 3.7 2.8 6.37 6.50 6.25 77.0 90.0 ... 90.0 1.0 65.535003 65.535003 65.535003 18.7 18.7 18.7 0.0 65535.0
2024-03-02 04:54:00 Raw 1.0 3.2 3.8 2.7 6.33 6.47 6.25 64.0 78.0 ... 77.0 1.0 65.535003 65.535003 65.535003 18.7 18.7 18.7 0.0 65535.0
2024-03-02 04:55:00 Raw 1.0 3.4 3.8 3.1 6.51 6.56 6.42 89.0 94.0 ... 98.0 1.0 65.535003 65.535003 65.535003 18.7 18.7 18.7 0.0 65535.0
2024-03-02 04:56:00 Raw 1.0 3.5 3.9 3.2 6.44 6.56 6.36 82.0 92.0 ... 92.0 1.0 65.535003 65.535003 65.535003 18.7 18.7 18.7 0.0 65535.0

5 rows × 31 columns

In [6]:
df.columns
Out[6]:
Index([u'Source', u'Values', u'MitVwind', u'MaxVwind', u'MinVwind',
       u'MitNRotor', u'MaxNRotor', u'MinNRotor', u'MitP', u'MaxP', u'MinP',
       u'GoPos', u'Hour', u'Wexp', u'Minutes', u'MitQ', u'MaxQ', u'MinQ',
       u'PavaVwind', u'PavaTech', u'PavaForceM', u'PavaExtern', u'Pitch',
       u'?1', u'?2', u'?3', u'?4', u'?5', u'?6', u'?7', u'?8'],
      dtype='object')

Daten der letzten 12 Stunden

In [7]:
fig=plt.figure(figsize=(12,8))
plt.plot(df[u'MitVwind'],df[[u'MitP']],'.')
x1,x2,y1,y2 = plt.axis()
v=np.linspace(0,10,100)
power=2.9*v**3
plt.plot(v,power)
plt.axis((0,x2,0,3300));
plt.xlabel("Windgeschw. [m/s]")
plt.ylabel("Leistung [kW]");
plt.title(u'Leistung über Windgeschwindigkeit');
In [8]:
fig=plt.figure(figsize=(8,8))
ax = plt.subplot(111, projection='polar')
ax.set_theta_direction(-1)
ax.plot(np.pi+df[[u'GoPos']].values*np.pi/180,df[[u'MitVwind']].values,'x')
ax.set_theta_zero_location("N")
ax.set_rmax(20)
ax.grid(True)
plt.title('Windrichtung und Windgeschwindigkeit [m/s]');
In [9]:
fig=plt.figure(figsize=(8,8))
ax = plt.subplot(111, projection='polar')
ax.set_theta_direction(-1)
ax.plot(np.pi+df[[u'GoPos']].values*np.pi/180,(df.index.values.max()-df.index.values).astype(float)/(1000000000*3600),'o')
ax.set_theta_zero_location("N")
ax.set_rmax(15)
ax.grid(True)
plt.title(u'Windrichtung über die verstrichene Zeit [h] ("Rauchfahne")');

Daten der letzten 12 Stunden

In [10]:
df[[u'PavaExtern','MitP','PavaVwind']].plot(rot=45,ylim=(0,3500),figsize=(14,6))
plt.title(u'Leistungen [kW] über die Zeit bis '+str(df.index[-1]));
plt.savefig('Egneos-Power-letzten12Stunden.png')
In [11]:
df['Wexp'].plot(rot=45,figsize=(14,6));
plt.title(u'Stromzähler in kWh');

Daten der letzten 7 Tage

In [12]:
df = pd.read_sql_query('SELECT *'
                       ' FROM Wecstd_Rep limit 1008 offset (select count(*) FROM Wecstd_Rep)-1008'
                       , disk_engine)
#aller 10Min.:6 pro Stunde mal 24 mal 7 = 1008
df.index=pd.DatetimeIndex(df["index"])
df.drop('index', axis=1, inplace=True)
print "df-Shape: ",df.shape
df-Shape:  (1008, 31)
In [13]:
df[[u'PavaExtern','MitP','PavaVwind']].plot(rot=45,ylim=(0,3500),figsize=(14,6))
plt.title(u'Leistungen [kW] über die Zeit bis '+str(df.index[-1]));
plt.savefig('Egneos-Power-letzten7Tage.png')
In [14]:
df['Wexp'].plot(rot=45,figsize=(14,6));
plt.title(u'Stromzähler in kWh');

Daten des letzten 30 Tage

In [15]:
df = pd.read_sql_query('SELECT *'
                       ' FROM Wecstd_Rep limit 4320 offset (select count(*) FROM Wecstd_Rep)-4320'
                       , disk_engine)
#aller 10Min.:6 pro Stunde mal 24 mal 30 = 4320
df.index=pd.DatetimeIndex(df["index"])
df.drop('index', axis=1, inplace=True)
print "df-Shape: ",df.shape
df-Shape:  (4320, 31)
In [16]:
df[[u'PavaExtern','MitP','PavaVwind']].plot(rot=45,ylim=(0,3500),figsize=(14,6))
plt.title(u'Leistungen [kW] über die Zeit bis '+str(df.index[-1]));
plt.savefig('Egneos-Power-letzten30Tage.png')
In [17]:
df['Wexp'].plot(rot=45,figsize=(14,6));
plt.title(u'Stromzähler in kWh');

Langzeitdaten

In [18]:
df = pd.read_sql_query('SELECT *'
                       ' FROM Wecstd_Day'
                       , disk_engine)
#aller 10Min.:6 pro Stunde mal 24 mal 30 = 4320
df.index=pd.DatetimeIndex(df["index"])
df.drop('index', axis=1, inplace=True)
print "df-Shape: ",df.shape
df-Shape:  (20372, 31)
In [19]:
df[[u'PavaExtern','MitP','PavaVwind']].plot(rot=45,ylim=(0,3500),figsize=(14,6))
plt.title(u'Leistungen [kW] über die Zeit');
plt.savefig('Egneos-Power-alleTage.png')
In [20]:
df['Wexp'].cumsum().plot(rot=45,figsize=(14,6));
plt.title(u'Stromzähler in kWh');
In [21]:
#Alle Daten
df = pd.read_sql_query('SELECT *'
                       ' FROM Wecstd_Rep '
                       , disk_engine)
df.index=pd.DatetimeIndex(df["index"])
df.drop('index', axis=1, inplace=True)
print "df-Shape: ",df.shape
df-Shape:  (405369, 31)
In [22]:
fig=plt.figure(figsize=(12,8))
plt.hist(df[[u'MitVwind']].values,normed=True,bins=20);
x1,x2,y1,y2 = plt.axis()
plt.axis((0,x2,0,y2));
mean=df[[u'MitVwind']].mean().values[0]
plt.axvline(mean);
rv = scipy.stats.exponweib(1.0, 2.05, scale=7.7)  #Prognose mit mittel. vWind 7.7m/s, k=2.05 und x=1 ?
x_range = np.arange(0, 30, 0.1)
plt.plot(x_range, rv.pdf(x_range));
plt.title('Verteilung der Windgeschw. und mittlere bei ' +"%.2f"% mean +" m/s")
plt.xlabel("Windgeschw. [m/s]")
plt.ylabel("Wahrsscheinlichkeit"); #ToDo Weibull-Verteilung;
In [23]:
fig=plt.figure(figsize=(12,8))
plt.plot(df[u'MitVwind'].values  ,df[[u'MitNRotor']].values,'.')
plt.xlabel(u'Mittlere Windgeschw. [m/s]')
plt.ylabel(u'Mittlere Rotordrehzahl [U/min]')
x1,x2,y1,y2 = plt.axis()
plt.axis((0,x2,0,y2));
plt.title(u'Rotordrehzahl über Windgeschwindigkeit');