Sprache auswählen

In dieser Folge von Artikeln wird der Raspberry Pi dazu genutzt, Daten über verschiedene Sensoren zu erfassen, diese mit Datenbanken verwalten und anschließend verständlich darzustellen.

Der Raspberry Pi wird hier mit einem Temperatur und Feuchtesensor ausgestattet, der die Daten für einen Raum erfasst. Diese Informationen schreiben ich dann in eine Datenbank.

Um zusätzlich noch zu wissen, was denn draußen wettermäßig vor sich geht, habe ich noch über den Dienst Open Weather Map eine Wetter App integriert.

Mittels Kivy werden die Daten dann visualisiert. Das Ergebnis sieht dann so aus:

 

 

Inhalt

  1. Hardware für das Projekt
  2. Messdaten erfassen
  3. Kivy Framework für die Visualisierung erstellen
  4. Temperatur und Feuchte anzeigen
  5. Ein Menü hinzufügen
  6. Die Wettervorhersage integrieren
  7. Pollen App einfügen
  8. Quellcode auf GitHub

Die Hardware

Die Hardware ist schnell beschafft. Ich verwende:

  • Raspberry Pi 3B +
  • Raspberry Pi Netzteil
  • Das offizielle Raspberry Pi 7" Touch Display
  • DHT 11 Temperatur und Feuchte Sensor
  • Lochrasterplatine oder Bread Board
  • 2kOhm Widerstand
  • Kabel

Damit hat man eigentlich alles, was man benötigt. Wer sein Display jetzt noch an einen prominenten Platz stellen möchte, kann sich dafür diverse Gehäuse kaufen oder sich selbst einen einfachen Ständer drucken. Die CAD Daten meiner Ständer kann man hier (*) runterladen und sich drucken lassen. Dafür findet man mittlerweile genug Dienstleister.

(*) Beim Download dieses Bauteils geht ein Teil des Preises an mich

Messdaten erfassen

Die Messdaten zur Temperatur und Feuchtigkeit erfasse ich mit einem DHT11. Die Daten werden dann zur weiteren Verarbeitung in eine Datenbank, sowie eine JSON Dateie geschrieben. Die detailierte Beschreibung gibt es in diesem Artikel:

Temperatur und Feuchtedaten in DB und JSON Datein speichern

Ich versuche hier die Datenerfassung von der Visualisierung zu trennen. das passiert dann in einem nächsten Schritt.

Kivy Framework für die Visualisierung erstellen

Die Daten werden in einer Kivy App verarbeitet und dargestellt. Zu Anfang erstelle ich ein einfaches Framework, dass dann Schritt für Schritt mit den Funktionalitäten angereichert wird.

Das Framework beinhaltet zunächst nur ein Hautpmenü, von dem aus man sich auf die weiteren Menüs navigieren kann:

Kivy Framework zur Visualisierung erstellen

Temperatur und Feuchte anzeigen

Das Framework sollten wir jetzt am Laufen haben. Anschließend sollen die Messdaten dargestellt werden. Um damit zu experimentieren, und eine wiederverwendbare Vorlage zu haben, erstelle ich hier einmal ein einfaches Kivy Widget, das zwei Messdaten grafisch anzeigt. Außerdem lernen wir, wie wir die Erscheinung des Widgets einfach an unsere Wünsche anpassen können.

Einfaches Widget zur Anzeige von Temperatur und Feuchte

Matplotlib Widget in Kivy mit zwei Plots erstellen

Nachdem wir verstanden haben, wie das Widget grundsätzlich funktioniert, integrieren wir es in unser Framework.

Dazu tun wir das Folgende:

  • Das kv File wird mit den Zeilen für das Widget erweitert
  • Im Screen 2 fügen wir das Widget hinzu und passen die Größe an den vorhandenen Platz an
  • Außerdem binden wir den Text des Labels lbl2 an den time_code der Messwerte. So sieht man immer, wie aktuell die Messdaten sind
#Screen 2 displays the temperature and humidity
<Scrn2>:
    name: 'scrn2'
    Button:
        id: back
        text: 'Hauptmenu'
        on_press:
            root.manager.transition.direction = 'right'
            root.manager.goto_scrn1()
        size_hint: .1, .1
        pos_hint: {'top':1, 'left':1}
    Label:
        id: lbl1
        text: 'Wohnzimmer'
        size_hint: .8, .1
        pos_hint: {'top':1, 'center_x':.5}
    Label:
        id: lbl2
        text: 'Zeitstempel: ' + root.ids.widget1.val3
        size_hint: 1, .1
        pos_hint: {'bottom':1, 'center_x':.5}
    ScrollView:
        id: scroll1
        size_hint: 1, .85
        pos_hint: {'top': .85, 'left':1}
        GridLayout:
            id: grid1
            size_hint: 1, None
            height: self.minimum_height
            cols: 1
            row_force_default: True
            row_default_height: root.height * 0.85
            TwoScalesWidget:
                id: widget1
                size_hint: 1, .8
                pos_hint: {'top':.9, 'left': 1}
            TwoPlotsSharedXWidget:
                id: widget2
                units : ['°C','%rH']
                titles : ['Temperatur','Luftfeuchtigkeit']
                ax_colors : ['r','b']
                sourceX: root.timestamp
                sourceY: [root.temperature, root.humidity]
                formatter: '%d-%B'
                size_hint: 1, .8
                pos_hint: {'top':.9, 'left': 1}
  • Da ich davon ausgehe, dass die Messwerte zyklisch geändert werden und sich daher die Anzeige immer wieder ändern soll, sorgen wir dafür, dass die Funktionen zum Updaten regelmäßig ausgeführt werden.. Dazu bietet uns Kivy die Clock Funktionalität. In meinem Beispiel setze ich das Interval zur Aktualisierung auf  Sekunden, da ich annehme, dass sich die Temperatur in einem Zimmer nicht so schnell ändert. Dazu müssen wir lediglich die Klasse Scrn2 anpassen und die clock.schedule Funktion beim Aufruf von __init__ einbinden. Zusätzlich zum Regelmäßigen Aufrufen der Update Funktionen soll das Widget natürlich gleich nach dem Start der App die aktuellen Daten anzeigen. Dazu wird ebenfalls in der __init__ Funktion ein einmaliger Call der Update Funktionen gestartet.
from kivy.clock import Clock
class Scrn2(Screen):
    '''
    Shows the screen containing the temperature and humidity widget
    Attributes:
        None
    '''

    timestamp = ListProperty([])
    temperature = ListProperty([])
    humidity = ListProperty([])

    def __init__(self, **kwargs):
        '''
        Start updating the screen regularly.
        A clock will call the update function in a selectable interval.
        Put all functions you want to update into the update function
        Args:
            **kwargs (): not used. For further development.
        Returns:
            Nothing.
        '''
        super(Scrn2, self).__init__(**kwargs)
        Clock.schedule_interval(self.update_scales, 60)
        Clock.schedule_once(self.update_scales)
        Clock.schedule_interval(self.update_graph, 60)
        Clock.schedule_once(self.update_graph)

    def update_scales(self, dt):
        '''
        updates the scales
        Args:
            dt (int): interval in seconds in which the funtion will be called
        Returns:
            nothing
        '''
        low1 = float(App.get_running_app().config.get('Two Scales Widget', 'temp_lower_limit'))
        high1 = float(App.get_running_app().config.get('Two Scales Widget', 'temp_upper_limit'))
        low2 = float(App.get_running_app().config.get('Two Scales Widget', 'humidity_lower_limit'))
        high2 = float(App.get_running_app().config.get('Two Scales Widget', 'humidity_upper_limit'))

        filename = App.get_running_app().config.get('Two Scales Widget', 'data_source_scales')

        self.ids.widget1.show_data(filename=filename, low1=low1, high1=high1, low2=low2, high2=high2)

    def update_graph(self, dt):
        '''
        updates the plot
        Args:
            dt (int): interval in seconds in which the funtion will be called
        Returns:
            nothing
        '''

        # Read the data to show from a file and store it
        filename = App.get_running_app().config.get('Two Scales Widget', 'data_source_graph')

        try:
            with open(filename, 'r') as read_file:
                data = json.load(read_file)
                print(data)
        except FileNotFoundError:
            print('File not found for temperature and humidity graph')
            return

        self.timestamp.clear()
        self.temperature.clear()
        self.humidity.clear()

        for item in data:
            self.timestamp.append(datetime.fromtimestamp(mktime(strptime(item['time_code'], '%Y-%m-%d %H:%M:%S'))))
            self.temperature.append(float(item['temperature']))
            self.humidity.append(float(item['humidity']))

        self.ids.widget2.update_plot()

So. fertig. Wenn wir den Code ausführen und uns zum Wohnzimmer navigieren, wird uns die Temperatur und Feuchte angezeigt, sowie unten im Bildschirm der Zeitstempel, an dem die Messwerte aufgenommen wurden. Durch Scrollen nach unten kommt der Graph mit der Temperatur und Feuchte über die letzten zwei Tage zum Vorschein.

Ein Menü hinzufügen

Wer den Code aufmerksam gelesen hat, wird gemerkt haben, dass einige Werte, wie bspw. die Grenzen der Skalen nicht hart vorgegeben werden, sondern über solch einen Call gezogen werden.

App.get_running_app().config.get('Two Scales Widget', 'temp_lower_limit')

Damit greift man in Kivy auf Menüs zu. Diese fügen wir jetzt ein. Darüber lässt sich dann für den Benutzer einstellen, welche Stadt für die Wettervorhersage herangezogen werden soll etc. 

Das grundlegende Vorgehen im Umgang mit Menüs in Kivy beschreibt dieser Artikel.

Als erstes benötigen wir die .ini Datei und die .json Dateien um das Menü zu konstruieren. In meinem Fall sehen die so aus und liegen direkt im Verzeichnis der App ab.

mysettings.ini

[Weather Widget]
city = Berlin,de

[Two Scales Widget]
temp_upper_limit = 40
temp_lower_limit = 15
humidity_upper_limit = 90
humidity_lower_limit = 20

settings_two_scales_widget.json

[
    {
        "type": "numeric",
        "title": "Temperatur Obergrenze",
        "desc": "Obergrenze für Temperatur",
        "section": "Two Scales Widget",
        "key": "temp_upper_limit"
    },
    {
        "type": "numeric",
        "title": "Temperatur Untergrenze",
        "desc": "Untergrenze für Temperatur",
        "section": "Two Scales Widget",
        "key": "temp_lower_limit"
    },
    {
        "type": "numeric",
        "title": "Feuchtigkeit Obergrenze",
        "desc": "Obergrenze für Feuchtigkeit",
        "section": "Two Scales Widget",
        "key": "humidity_upper_limit"
    },
    {
        "type": "numeric",
        "title": "Feuchtigkeit Untergrenze",
        "desc": "Untergrenze für Feuchtigkeit",
        "section": "Two Scales Widget",
        "key": "humidity_lower_limit"
    }
]

settings_weather_widget.json

[
    {
        "type": "string",
        "title": "Stadt",
        "desc": "Vorhersage für Stadt:",
        "section": "Weather Widget",
        "key": "city"
    }
]

Modifikationen am .kv file

Am .kv file müssen zwei kleine Modifikationen vorgenommen werden. Da wir jetzt aus den Einstellungen, also der Klasse App heraus die Widgets ansprechen wollen, benötigen die beiden betroffenen Screens eine id, um das zu tun.

 

 

Änderungen am .py file

Zunächst müssen wir die Module für das Menü einbinden.

Anschließend wird das Menü in der Klasse MyVisuApp eingebaut.  Als erstes wird in der Funktion build() die mysettings.ini eingelesen. Anschließend wird in der Funktion build_settings() vorgegeben, dass die Menüstruktur um unsere beiden settings Dateien erweitert wird.

Um jetzt noch dynamisch auf Änderungen reagieren zu können, wird in der Funktion on_config_change() bei Änderung der Werte dafür gesorgt, dass die betroffenen Widgets geupdated werden.

class MyVisuApp(App):
    def build(self):
        '''
        overwrites the build() function.
        The appearance of the settings is set here.
        Choose from the available layouts: https://kivy.org/doc/stable/api-kivy.uix.settings.html#different-panel-layouts
        The preset values for the settings are loaded by the config.read() function
        Args:
            None
        Returns:
            class MyScreens().
        '''

        self.settings_cls = SettingsWithSidebar

        fileDir = os.path.dirname(os.path.abspath(__file__))
        absFilename = os.path.join(fileDir, 'mysettings.ini')
        self.config.read(absFilename)
        return MyScreens()

    def build_settings(self, settings):
        '''
        overwrites the build_settings() function.
        Add all necessary panels here by loading from the corresponding file.
        Args:
            settings
        Returns:
            Nothing.
        '''

        fileDir = os.path.dirname(os.path.abspath(__file__))
        absFilename1 = os.path.join(fileDir, 'settings_weather_widget.json')
        absFilename2 = os.path.join(fileDir, 'settings_two_scales_widget.json')
        settings.add_json_panel('Weather Widget', self.config, absFilename1)
        settings.add_json_panel('Two Scales Widget', self.config, absFilename2)

    def on_config_change(self, config, section, key, value):
        '''
        overwrites the on_config_change() function.
        define actions that shall happen when specific entries in the configuration change here.
        Args:
            config (kivy.config.ConfigParser):
                current configuration.
            section (str):
                name of the section where the key belongs to.
            key (str):
                key as specified in the json panels.
            value ():
                value of the key. return value depending on the type of variable.

Und so in etwa sollte dann das Ergebnis aussehen.

Die Wettervorhersage integrieren

Auch für die Wettervorhersage schreiben wir uns ein Widget, dass wir in beliebigen Apps wiederverwenden können. Unser Widget wird sich die Daten Wetterdaten über die OpenWeatherMap API besorgen und dann darstellen. Die Details sind im folgenden Artikel beschrieben.

Wetter App mit Kivy und OpenWeatherMap API in Python

Nachdem das Widget funktionsfähig ist, gehen wir genau so vor, wie mit der Anzeige der Temperatur und Feuchtedaten.

  • Das kv File wird mit den Zeilen für das Widget erweitert
  • Im Screen 3 fügen wir das Widget hinzu und passen die Größe an den vorhandenen Platz an
  • Außerdem binden wir den Text des Labels lbl2 an die notification, um mitzubekommen, falls mal Daten fehlen
#Screen 3 displays the weather forecast
<Scrn3>:
    name: 'scrn3'
    Button:
        id: back
        text: 'Hauptmenu'
        on_press:
            root.manager.transition.direction = 'right'
            root.manager.goto_scrn1()
        size_hint: .1, .1
        pos_hint: {'top':1, 'left':1}
    Label:
        id: lbl1
        text: 'Vorhersage'
        size_hint: .8, .1
        pos_hint: {'top':1, 'center_x':.5}
    Label:
        id: lbl2
        text: root.ids.widget1.notification
        size_hint: 1, .1
        pos_hint: {'bottom':1, 'center_x':.5}
    WeatherWidget:
        id: widget1
        size_hint: 1, .8
        pos_hint: {'top':.9, 'left': 1}

Auch hier müssen wir im .py file noch ein paar Anpassungen machen. Zunächst fügen wir die notwendigen Aufrufe im Scrn3 hinzu und stellen auch hier sicher, dass die Anzeige in regelmäßigen Intervallen aktualisiert wird. Dazu gehen wir gleich vor, wie bei der Anzeige der Temperatur und Feuchtedaten. Man muss hier nur beachten, dass das Update in längeren Intervallen erfolgen sollte, so dass die OpenWeatherMap API einen nicht sperrt, weil man zu häufig anfragt.

class Scrn3(Screen):
    def __init__(self, **kwargs):
        '''
        Start updating the screen regularly.
        A clock will call the update function in a selectable interval.
        Put all functions you want to update into the update function
        Args:
            **kwargs (): not used. For further development.
        Returns:
            Nothing.
        '''
        super(Scrn3, self).__init__(**kwargs)
        Clock.schedule_interval(self.update, 1800)
        Clock.schedule_once(self.update)

    def update(self, dt):
        '''
        calls funtions to update the screen.
        Args:
            dt (int): interval in seconds in which the funtion will be called
        Returns:
            (float): Scaled value.
        '''
        city = App.get_running_app().config.get('Weather Widget', 'city')
        self.ids.widget1.download_current_weather(city=city)
        self.ids.widget1.download_forecast(city=city)

So sieht dann das Ergebnis aus

Pollen App einfügen

Und jetzt?

Wer noch weiter machen will kann sich hier ansehen, wie man die Werte mittels Web Visu von jedem Browser aus zugänglich macht

Quellcode auf GitHub

Der aktuelle und kommentierte Quellcode steht euch auf GitHub zum Download, Weiterentwickeln und Kommentieren zur Verfügung.

https://github.com/HaikoKrais/HomeDataViewer