WattWatch/HauptProgramm.py

332 lines
14 KiB
Python

# main.py
#
# Copyright 2023 Nikola Mitrojevic, Emanuel Loos & Tobias Bramböck
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Import statements
import kivy
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from datetime import datetime, date, timedelta
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.scrollview import ScrollView
from kivy.garden.matplotlib import FigureCanvasKivyAgg
from awattar import AwattarClient
import json
import requests
from datetime import datetime, timedelta
# Create AwattarClient instance
client = AwattarClient('AT')
# Set initial date and get energy prices
date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
data = client.request(date)
hours = np.arange(24)
prices = np.array([item.marketprice/10 for item in data])
# Define DiagramWidget class
class DiagramWidget(FigureCanvasKivyAgg):
def __init__(self, figure, **kwargs):
super().__init__(figure, **kwargs)
self.ax = self.figure.axes[0]
figure.patch.set_facecolor('black')
self.minimum_height = 100 # Set a minimum height for the widget
self.ax.set_facecolor('black') # set background color to black
self.ax.tick_params(colors='white') # set tick color to white
for spine in self.ax.spines.values():
spine.set_edgecolor('white') # set spine edge color to white
spine.set_facecolor('black') # set spine face color to none
# Define MyBoxLayout widget
class MyBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Set widget properties
self.orientation = 'vertical'
self.background_color = (0, 0, 0, 1) # set background color to black
label_button_layout = BoxLayout(orientation='horizontal', size_hint_y=None, height='50dp')
label_button_layout.add_widget(Label(text='[b]Strompreise & Stromerzeugung[/b]', markup=True, halign='left', valign='top', font_size='20sp', size_hint=(self.width, None), size=(0, 50), pos_hint={'x':0,'y':0}))
picture_button = Button(size_hint=(None, None), size=('50dp', '60dp'), pos_hint={'x': 0.8, 'y': 0})
picture_button.background_normal = 'Einstellungsrad.svg'
picture_button.bind(on_release=self.open_settings)
label_button_layout.add_widget(picture_button)
self.add_widget(label_button_layout)
self.date_label = Label(text=date.strftime('%d.%m.%Y'), font_size='20sp', halign='left', valign='middle', size_hint=(1, None), height='50dp', pos_hint={'top': 1.0, 'x': 0.02})
self.add_widget(self.date_label)
# Define color map based on energy prices
cmap = mpl.colors.ListedColormap(['blue', 'green', 'yellow', 'red'])
bounds = [-float('inf'), 0, 5, 20, float('inf')] # Include 0 in color map bounds
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
color_map = cmap(norm(prices))
# Create plot for the second diagram
def get_prices_and_dates(date):
# Construct the URL with the specified date
next_date = (date + timedelta(days=1)).strftime('%Y-%m-%d')
url = f'https://transparency.apg.at/transparency-api/api/v1/Data/EXAAD1P/German/M15/{date.strftime("%Y-%m-%d")}T000000/{next_date}T000000'
# Download the JSON data from the URL
response = requests.get(url)
data = response.json()
quaterly_prices = []
time_labels = []
for item in data['ResponseData']['ValueRows']:
quarter_hour = item['TF']
price = item['V'][0]['V']
quaterly_prices.append(price)
time_labels.append(quarter_hour)
return time_labels, quaterly_prices
# Plot prices for the current date
time_labels, quaterly_prices = get_prices_and_dates(date)
plt.figure(figsize=(10, 5))
plt.plot(time_labels, quaterly_prices, marker='o')
plt.xlabel('Time')
plt.ylabel('EXAAD1Price')
plt.title('EXAAD1Price for every quarter of an hour')
plt.xticks(rotation=45)
plt.grid(True)
# Create DiagramWidget and add it to the ScrollView
self.second_diagram_widget = DiagramWidget(figure=plt.gcf())
scroll_view_2 = ScrollView(size_hint=(1, 0.3)) # Make ScrollView take up 30% of screen height
scroll_view_2.add_widget(self.second_diagram_widget)
self.add_widget(scroll_view_2)
# Create plot figure
self.fig, self.ax = plt.subplots(figsize=(10, 5))
self.fig.subplots_adjust(left=0.1375, bottom=0.175) # Adjust bottom margin to make space for buttons
# Add bars to plot using color map
for i in range(len(hours)):
self.ax.bar(hours[i], prices[i], width=0.5, edgecolor='white', linewidth=0.5, color=color_map[i])
# Set plot limits and ticks
self.ax.set(xlim=(-1, 24), xticks=hours)
# Create DiagramWidget and add it to the ScrollView
self.diagram_widget = DiagramWidget(figure=self.fig)
scroll_view = ScrollView(size_hint=(0.985, 0.3)) # Make ScrollView take up 30% of screen height
scroll_view.add_widget(self.diagram_widget)
self.add_widget(scroll_view)
# Add toggle buttons
toggle_box = BoxLayout(orientation='horizontal', size_hint=(1, 0.1)) # Make toggle_box take up 10% of screen height
toggle_box.spacing = '5dp'
toggle_box.padding = '5dp'
toggle_button1 = ToggleButton(text='Netto/Brutto')
toggle_button2 = ToggleButton(text='Stromherkunft einblenden')
toggle_box.add_widget(toggle_button1)
toggle_box.add_widget(toggle_button2)
self.add_widget(toggle_box)
# Add navigation buttons
button_box = BoxLayout(orientation='horizontal', size_hint=(1, 0.1)) # Make button_box take up 10% of screen height
button_box.spacing = '5dp'
button_box.padding = '5dp'
prev_button = ToggleButton(text='Prev Day')
prev_button.bind(on_press=self.previous_day)
next_button = ToggleButton(text='Next Day')
next_button.bind(on_press=self.next_day)
button_box.add_widget(prev_button)
button_box.add_widget(next_button)
self.add_widget(button_box)
# Update energy prices and plot
self.update_prices()
# Update energy prices and plot
def update_prices(self):
global prices, date
date_prices = client.request(date)
prices = np.array([item.marketprice/10 for item in date_prices])
# Define color map based on energy prices
cmap = mpl.colors.ListedColormap(['blue', 'green', 'yellow', 'red'])
bounds = [-float('inf'), 0, 5, 20, float('inf')] # Include 0 in color map bounds
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
color_map = cmap(norm(prices))
# Clear existing plot and add bars using color map
self.ax.clear()
for i in range(len(hours)):
self.ax.bar(hours[i], prices[i], width=0.5, edgecolor='white', linewidth=0.5, color=color_map[i])
# Set plot limits and ticks
self.ax.set(xlim=(-1, 24), xticks=hours)
self.date_label.text = date.strftime('%d.%m.%Y')
self.diagram_widget.draw()
# Navigate to previous day and update energy prices and plot
def previous_day(self, instance):
global date
date = date - timedelta(days=1)
self.update_prices()
# Navigate to next day and update energy prices and plot
def next_day(self, instance):
global date
date = date + timedelta(days=1)
self.update_prices()
# Dynamically adjust height of ScrollView and DiagramWidget
def on_size(self, *args):
self.diagram_widget.height = max(self.height * 0.6 - 100, self.diagram_widget.minimum_height) # Adjust height to make space for toggle_box and button_box
self.diagram_widget.width = self.width
def open_settings(self, instance):
app = App.get_running_app()
app.root.current = 'settings'
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.dropdown = DropDown()
self.create_dropdown()
self.create_input_boxes()
def create_dropdown(self):
main_button = Button(text='Select Option', size_hint=(None, None), size=(0, 0), pos_hint={'x': -50, 'y': -50})
main_button.bind(on_release=self.dropdown.open)
self.dropdown.bind(on_select=self.select_option)
options = ['Option 1', 'Option 2', 'Option 3']
for option in options:
btn = Button(text=option, size_hint_y=None, height=44)
btn.bind(on_release=lambda btn: self.dropdown.select(btn.text))
self.dropdown.add_widget(btn)
self.add_widget(main_button)
def select_option(self, instance, text):
print('Selected option:', text)
def create_input_boxes(self):
scroll_view = ScrollView()
input_box_layout = BoxLayout(orientation='vertical', size_hint_y=None, spacing=10, padding=10)
input_box_layout.bind(minimum_height=input_box_layout.setter('height'))
fixed_fees_kwh = TextInput(hint_text=f'Fixkosten pro kWh brutto', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(fixed_fees_kwh)
tax_percent = TextInput(hint_text=f'Steuer in Prozent', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(tax_percent)
latitude = TextInput(hint_text=f'Breitengrad[°]', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(latitude)
gps_latitude_button = Button(text='GPS', size_hint=(None, None), size=('50dp', '60dp'), pos_hint={'x': 0, 'y': 0})
input_box_layout.add_widget(gps_latitude_button)
longitude = TextInput(hint_text=f'Längengrad[°]', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(longitude)
gps_longitude_button = Button(text='GPS', size_hint=(None, None), size=('50dp', '60dp'), pos_hint={'x': 0, 'y': 0})
input_box_layout.add_widget(gps_longitude_button)
azimuth = TextInput(hint_text=f'Azimut', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(azimuth)
tilt = TextInput(hint_text=f'Neigung', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(tilt)
peak = TextInput(hint_text=f'Peakleistung der Zellen', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(peak)
surface = TextInput(hint_text=f'Fläche der Zellen', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(surface)
efficiency = TextInput(hint_text=f'Wirkungsgrad der Zellen', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(efficiency)
temp_coeff = TextInput(hint_text=f'Temperaturkoeffizient', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(temp_coeff)
efficiency_dif = TextInput(hint_text=f'Effizienz diffuse Strahlung', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(efficiency_dif)
albedo = TextInput(hint_text=f'Albedo', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(albedo)
invert_perf = TextInput(hint_text=f'Wechselrichterleistung', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(invert_perf)
invert_eff = TextInput(hint_text=f'Wechselrichtereffizient', size_hint=(1, None), height='40dp')
input_box_layout.add_widget(invert_eff)
scroll_view.add_widget(input_box_layout)
# Create a vertical box layout to hold the "Back" button and the scroll view
content_layout = BoxLayout(orientation='vertical')
# Create the "Back" button
back_button = Button(text='Back', size_hint=(None, None), size=('100dp', '50dp'))
back_button.bind(on_release=self.go_back)
# Add the "Back" button to the content layout
content_layout.add_widget(back_button)
# Add the scroll view to the content layout
content_layout.add_widget(scroll_view)
# Add the content layout to the screen
self.add_widget(content_layout)
def go_back(self, instance):
app = App.get_running_app()
app.root.current = 'WattWatchMain'
# Define WattWatchMain widget
class WattWatchMain(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
layout = MyBoxLayout()
self.add_widget(layout)
# Define WattWatchApp class
class WattWatchApp(App):
title = 'WattWatch'
icon = 'wwicon.png'
def build(self):
sm = ScreenManager()
my_screen = WattWatchMain(name='WattWatchMain')
sm.add_widget(my_screen)
sm.current = 'WattWatchMain'
return sm
# Run the app
if __name__ == '__main__':
WattWatchApp().run()