Meteor Neowise

Gestern habe ich mehr oder weniger ein Schnappschuss vom Meteor Neowise gemacht. Dieser ist nur alle paar tausend Jahre zu sehen, daher wollte ich die Chance war nehmen und auch mich in die Liste derer eintragen, die von dem Meteor ein Foto gemacht haben. Mit der App Star Walk 2 konnte ich Neowise virtuell direkt aufspüren und wusste wo er ungefähr sein müsste. Auch wenn ich aus dem Büro fotografiert hatte, so kann ich mit dem Foto sehr gut leben :). Eigentlich wollte ich um 23:30 schon aufgeben, da ich den Meteor nicht am Himmel sah, konnte dann aber doch einen hauch dünnen Schweif am Himmel sehen. Ich hielt die Linse also drauf und tatsächlich, mit einer längeren Verschlusszeit ließ sich der Meteor ganz gut erkennen. Also Kamera wieder aufs Stativ. Am Fenster musste ich das Stativ provisorisch befestigen, um eine ruhige Position zu erhalten. Ich bin froh, dass diese Konstellation gehalten hat 🙂

Auf der Canon war mein Sigma 24mm Weitwinkelobjektiv. Auf Blende 1.4 hatte ich für knappe 30 Sekunden belichtet und dieses Bild ist dabei rum gekommen. 

Ich bin sehr froh, dass ich das Bild geschossen habe. Es ist sehr interessant, Neowise auf ein eigenes Bild festgehalten zu haben. Bis zum Ende des Monats soll man ihn noch sehen können, ehe er im August in den Tiefen der Galaxie verschwindet. Wer weiß vielleicht wage ich noch ein weiteres Foto an einem besseren Spot. 

Windows 10 Wlan Passwort auslesen

Habt ihr euch mal gefragt, wie ihr von einem zuvor verbundenen WLAN, das Passwort auslesen könnt, weil ihr es z.B. vergessen habt? Dafür gibt es 2 Wege. Natürlich muss eurem Gerät das Netzwerk vorher bekannt gewesen sein. Die erste führt über die Systemsteuerung. Lasst euch den Status eures drahtlosen Adapters anzeigen und geht dort auf Drahtloseigenschaften.

Es öffnet sich ein weiteres Fenster mit den Eigenschaften des drahtlosen Netzwerkes. Dort müsst ihr nur noch auf den Tab Sicherheit klicken und von dem Sicherheitsschlüssel die Zeichen anzeigen lassen.

Ein zweiter Weg geht über die Kommandozeile. Öffnet die CMD als Administrator und gebt dort folgenden Befehl ein.

netsh wlan show profiles <WLAN-NAME> key=clear

Eure Ausgabe könnte dann wie folgt aussehen, wobei unter dem Schlüsselinhalt das verwendete Wlan Passwort sein sollte.

Gesichtserkennung Raspberry GPIO Schaltung

Mich haben zuletzt vermehrt Anfragen erreicht, die GPIOs mit der Gesichtserkennung zu kombinieren. Zum Beispiel als Schaltung für die Garage oder das eine LED für eine bestimmte Zeit aktiviert ist. Zudem wäre eine Benachrichtung aufs Smartphone interessant. Diesen Anfragen möchte ich mit diesem Beitrag gerecht werden. Die Anforderung an mich selber waren, dass für jedes Gesicht, welches die Software im Frame erkennt, das Script eine Nachricht an das Smartphone schicken soll. Für jedes bekannte Gesicht soll zusätzlich die LED für 5 Sekunden laufen. Für die Smartphone Benachrichtigung habe ich Pushbullet verwendet. Pushbullet stellt eine App bereit und es stehen mehrere Python Module zur Verfügung. Ich habe die eigene von Pushbullet verwendet.

sudo pip3 install pushbullet.py

Für die Registrierung benötigt man einen Google oder Facebook Account.

Auf der Seite könnt ihr den API Key unter Access Tokens einsehen. Diesen Schlüssel übertragt ihr bei jeder Push Nachricht mit. Ich empfehle euch, die End-zu-End Verschlüsselung zu aktivieren. Ihr müsst ein einmaliges Passwort eintragen. Jedes Gerät das ihr bei Pushbullet registriert habt, benötigt diesen Schlüssel, um die Nachrichten zu entschlüsseln.

 

Für die Schaltung habe ich ein Steckbrett mit LED und Widerstand benutzt. Auf dem Pi ist der GPIO Pin 17 angeschlossen. Es ist euch selber überlassen, welchen Pin ihr nehmt, ihr müsst diesen nur vernünftig schalten können.

Damit hätten wir alles Organisatorische geregelt. Kommen wir nun also zum Code, der an die anderen Gesichtserkennungs-Beiträgen angelehnt ist. Ich habe hier zusätzlich Threads mit eingebaut, damit die Abarbeitung der Notifications oder die Schaltung der LED neben dem Main-Thread laufen können. Die Threads rufen jeweils Funktionen auf, daher besteht für die Notification, als auch für die LED Schaltung eine eigenständige Funktion, denen ich Argumente übergebe. Damit ein Thread nicht doppelt aufgerufen werden kann, habe ich mit if tgpio not in threading.enumerate(): eine Verriegelung eingebaut. threading.enumerate() enthält jede zurzeit aktive Thread-ID.

Funktion NotifyPushbullet:

Ein Foto wird zwar außerhalb der Funktion gemacht, aber immer dann, wenn das Script ein Gesicht im Frame entdeckt hat. Das Foto ist lokal gespeichert, bevor die Funktion NotifyPushbullet es verschickt. Ich lege diese Funktion danach noch 30 Sekunden „schlafen“, um eine weitere unnötige Notifications zu vermeiden.

Funktion FaceGPIO:

Die LED wird geschaltet, wenn ein bekanntes Gesicht im Frame ist. Sobald eines erkannt wurde, leuchtet die LED für 12 Sekunden, ehe sie sich selber wieder ausschaltet. Falls es bei den GPIOs zu Fehlern kommt, z.B. eine Doppelbelegung des Pins, löst eine Exception aus und gibt den Fehler im Terminal zurück.

Weiter unten findet ihr den Code. Ich habe einige Einträge kommentiert, die helfen sollen den Code einfacher zu verstehen.

# This program will print out the names of anyone it recognizes to the console.

# To run this, you need a Raspberry Pi 2 (or greater) with face_recognition and
# the picamera[array] module installed.
# You can follow this installation instructions to get your RPi set up:
# https://gist.github.com/ageitgey/1ac8dbe8572f3f533df6269dab35df65

try:
    import face_recognition
    import picamera
    import numpy as np
    import os
    import datetime
    from pushbullet import Pushbullet
    import gpiozero
    from time import sleep
    import threading

except:
    print("failed to load module")
    exit()


# Initialize some variables
tpush = None
tgpio = None
face_locations = []
face_encodings = []
image = []
known_face_encoding = []
known_faces = []

# Set some variables
pb = Pushbullet('apikey')
framerate = 15

def NotifyPushbullet(pb,file):
    #push notification
    push = pb.push_note("Face detected","Someone was detected")
    # push image
    with open(file, "rb") as pic:
    file_data = pb.upload_file(pic, "picture.jpg")
    push = pb.push_file(**file_data)
    # set NotifyPushbullet to sleep
    print("Push Notification sent")
    sleep(30)


# Function for GPIO
def FaceGPIO(timetosleep):
    try:
        #initialize LED
        led = gpiozero.LED(17)
        # switch LED on
        led.on()
        #set to sleep
        sleep(timetosleep)
        # switch LED off
        led.off()
    # if any error occurs call exception
    except gpiozero.GPIOZeroError as err:
        print("Error occured: {0}".format(err))

# Get a reference to the Raspberry Pi camera.
# If this fails, make sure you have a camera connected to the RPi and that you
# enabled your camera in raspi-config and rebooted first.
try:
    camera = picamera.PiCamera()
    camera.resolution = (320, 240)
    camera.framerate = framerate
    output = np.empty((240, 320, 3), dtype=np.uint8)
except:
    print("something went wrong while initialize camera")
    exit()

# Load pictures and learn how to recognize it.
print("Loading known face image(s)")

#is loading all images in faces/ and create a stamp
try:
    for faces,i in zip(os.listdir("faces"),range(len(os.listdir("faces")))):
        known_faces.append("faces/"+faces)
        image.append(face_recognition.load_image_file("faces/" + faces))
        known_face_encoding.append(face_recognition.face_encodings(image[i])[0])
except:
    print("could not find known pictures")
    exit()


while True:
    print("Capturing image.")
    # Grab a single frame of video from the RPi camera as a numpy array
    camera.capture(output, format="rgb")

    # Find all the faces and face encodings in the current frame of video
    face_locations = face_recognition.face_locations(output)
    print("Found {} faces in image.".format(len(face_locations)))
    face_encodings = face_recognition.face_encodings(output, face_locations)

    #print all active threads
    print(threading.enumerate())

    # Loop over each face found in the frame to see if it's someone we know.
    for face_encoding in face_encodings:

        # make a picture
        camera.capture('/home/pi/image.jpg', use_video_port=True)
        # create a thread to push Notifications
        # check if such a thread already exists
        if tpush not in threading.enumerate():
            #define thread
            tpush = threading.Thread(target=NotifyPushbullet,args=(pb,'/home/pi/image.jpg',))
            #start thread
            tpush.start()
        else:
            # if a thread was started already and still is running
            print("Thread tpush already started: {0}".format(str(tpush)))

        for face, i in zip(os.listdir("faces"), range(len(known_faces))):

            if face_recognition.compare_faces([known_face_encoding[i]], face_encoding,0.6)[0]:
                #print found face all upper case
                print("<found: ".upper() + known_faces[i].upper() + ">")

                #create a thread to switch the LED on
                # check if such a thread already exists
                if tgpio not in threading.enumerate():
                    #define thread; call function FaceGPIO with argument
                    tgpio = threading.Thread(target=FaceGPIO,args=(5,))
                    #start thread
                    tgpio.start()
                else:
                    # if a thread was started already and still is running
                    print("Thread tgpio already started: {0}".format(str(tgpio)))

Projekt: Asset Management Teil3 Metabase

Kommen wir nun zum dritten Teil dieses Projektes. In den vorherigen Teilen habe ich über das Projekt selber gesprochen, die Datenbankstruktur und generelle Scripte erläutert bzw. erwähnt. Im zweiten Teil kam Django dazu. In diesem Teil werde ich das Metabase Frontend etwas genauer zeigen. Eins vorweg, ich werde hier nicht alle Funktionen von Metabase beschreiben, sondern nur diejenigen, die ich zurzeit verwende oder eventuell später verwenden möchte. Metabase selber betreibe ich zurzeit mit 2 Dashboards. Eins für die Dividenden und eins für meine gehaltenen Aktien und meine Einkünfte. Sind wir auf der Startseite, so kann eins von beiden ausgewählt werden.

 

In meinen Dividenden Dashboard lasse ich mir durch native Fragen, die Dividende pro Jahr, pro Firma und pro Firma im Jahr anzeigen. Native Fragen sind SQL Abfragen, die ich soweit selber geschrieben habe. Solch Fragen können aber auch mithilfe der Metabase Tools gänzlich ohne SQL erzeugt werden.

Hier ist z.B. die SQL Abfrage für die Dividende pro Firma im Jahr:

select min(COMPANY.NAME), sum(DIVIDENT.PROFIT),year(DIVIDENT.RECEIVED_AT)  as YEAR, min(Currency.CURRENCY) from STOCK_DIVIDENT DIVIDENT
inner join STOCK_COMPANY COMPANY on DIVIDENT.COMPANY = COMPANY.ID
inner join Currency on DIVIDENT.CURRENCY = Currency.ID Group by DIVIDENT.COMPANY, YEAR(DIVIDENT.RECEIVED_AT);

 

Unter meinem Stock Dashboard gibt es etwas mehr zu sehen. Dort liste ich alle mein kompletten Aktien auf. Käufe und Verkäufe und den Gesamtwert, den ich mit Aktien bisher realisiert habe. Auch hier sind alle Anzeigen per SQL von mir selbst geschrieben. Hier als Beispiel die Query von dem Torten Diagramm:

select sum(T.QUANTITY) as QUANTITY,C.NAME from STOCK_AVAILABLE_STOCK T INNER JOIN STOCK_COMPANY C on T.COMPANY = C.ID group by T.COMPANY

 

Zudem können sogenannte Pulse erstellt werden. Mit einem Pulse könn ihr Daten per Mail oder Slack nach einem bestimmten Zeitplan versendeen. Bisher habe ich diese Funktion noch nicht verwendet, aber ich gehe davon aus, dass ich die Benachrichtigung ab einem bestimmten Zeitpunkt einrichten werde.

 

Falls ihr euch noch die vorherigen Teile vom Projekt anschauen wollt, so findet ihr den ersten Teil hier und den zweiten Teil hier.

 

Fazit

Das war es von vorerst von diesem Projekt. Ich habe viel wissenswertes mitgenommen und konnte mich etwas in Django einarbeiten. Für mich war und ist es ein interessantes Projekt und ich entwickel es definitiv noch weiter. Für mich bringt es mittlerweile schon einen erheblichen Mehrwert. Durch das Admin Interface ist die Pflege der Daten einfach und komfortable und durch die Skalierbarkeit von Metabase kann ich mir mehrere Dashboard erzeugen, die mir jeweils andere Einsichten geben.

 

 

Projekt: Asset Management Teil2 Django

Ein kurzer Rückblick zu Teil 1:

Das Projekt „Asset Management“ habe ich ins Leben gerufen, damit ich meine Investments zentral gespeichert habe. Dafür verwende ich Metabase. Die Datenbank Struktur ist im vorherigen Bericht zu sehen. Investiert habe ich in mehrere Arten, von P2P bis Aktien. Dabei haben alle ihre eigene Plattform, wo ich die Informationen bekomme. Diese Informationen liegen allerdings nicht zentral an einem Ort. Dies wollte ich mit diesem Projekt ändern.

Ich habe das Django Projekt „controlpanel“ genannt und 2 Apps in dem Projekt erstellt. Diese haben den Namen „Metabase“ und „Weather. Das integrierte Admin Interface von Django leistet mir hier große Dienste. Ich benutze es um händische Einträge in die Datenbank zu machen, aber auch um bereits vorhandene Datensätze abzuändern. Durch die Passwort Abfrage sind diese auch vor Unbefugten geschützt.

Das Controlpanel besteht aus 3 Segmenten, was soweit im Bild zu ersehen ist. Das Erste ist für die Weather Daten(Schnittstelle openweather). Dort kann ich, falls gewünscht, die Daten ändern. Ebenfalls gibt es hier den Link zu meinem Dashboard, wo ich verschiedene Auswertungen über die Wetterdaten fahren kann.

Das zweite Segment gehört zu Metabase und beinhaltet die Eingabe der Daten vom Windpark z.B. der eingespeiste Energieertrag, Windmühlen etc. Natürlich werden hier auch die Daten von meinem Aktienhandel eingetragen. Diese Daten dienen Metabase zur Darstellung. Auch in diesem Segment existiert, wie im Weather Segment, eine Verlinkung zum Metabase Frontend.

Das dritte Segment beinhaltet die Backups, die jeden Abend erstellt werden. Das Script schreibt dafür eine Log-Datei. Die Funktion „readbackups(file)“ in der function.py weiter unten liest diese Informationen aus und übermittelt diese dem Template zur Ausgabe.

 

Da ich bisher nur insgesamt 2 Apps habe, beschränken sich die URLs ebenfalls nur auf diese zwei.

urls.py:

from django.contrib import admin
from django.urls import include, path
from metabase import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',views.index, name='index'),
    path('metabase/', views.metabase, name='metabase'),
    path('weather/', views.weather, name='weather'),
]

 

functions.py:

import os
import random
import re

def readbackups(file):
  backup_dict = {'backup': {}}
  try:
    fileline = open(file,"r")
    for count,line in enumerate(fileline):
      #print(line)
      backup_dict['backup'][count] = line.split("/")
      if count == 0:
        #add a name to the total backuo
        backup_dict['backup'][0].append("Total")
        # cut the last two character(". ")
        backup_dict['backup'][count][0] =  backup_dict['backup'][count][0][:-2]
      # cut the last character(.)
      backup_dict['backup'][count][0] =  backup_dict['backup'][count][0][:-1]	

  except os.error as e:
        print(e)
        return {'error': e}
  
  return backup_dict


  

 

Am wichtigsten sind hier allerdings die Models. Mit den Models kann Django die Datenbankstruktur abbilden. Sie sind also sehr hilfreich für das Admin Interface. Für jede App gibt es eine Models.py im jeweiligen Ordner. Da diese Datei ziemlich lang ist, stelle ich nur einen Teil online.

from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
import datetime


class AuxProjects(models.Model):
    id = models.IntegerField(db_column='ID', primary_key=True)  # Field name made lowercase.
    datum = models.DateField(db_column='DATUM')  # Field name made lowercase.
    zins = models.DecimalField(db_column='ZINS', max_digits=4, decimal_places=2)  # Field name made lowercase.
    laufzeit = models.DecimalField(db_column='LAUFZEIT', max_digits=4, decimal_places=2)  # Field name made lowercase.
    anlage = models.DecimalField(db_column='ANLAGE', max_digits=4, decimal_places=2)  # Field name made lowercase.
    status = models.ForeignKey('AuxStatus', models.DO_NOTHING, db_column='STATUS')  # Field name made lowercase.
    score = models.ForeignKey('AuxScore', models.DO_NOTHING, db_column='SCORE')  # Field name made lowercase.
    auszahlung = models.DateField(db_column='AUSZAHLUNG')  # Field name made lowercase.
    bezahlt = models.DecimalField(db_column='BEZAHLT', max_digits=4, decimal_places=2)  # Field name made lowercase.
    currency = models.ForeignKey('CURRENCY', models.DO_NOTHING, db_column='CURRENCY')  # Field name made lowercase.
    uploaded_at = models.DateTimeField(db_column='UPLOADED_AT')  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'AUX_PROJECTS'

    def __str__(self):
        return 'AUX: {}'.format(self.id)


class AuxScore(models.Model):
    id = models.AutoField(db_column='ID', primary_key=True)  # Field name made lowercase.
    name = models.CharField(db_column='NAME', max_length=50)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'AUX_SCORE'

    def __str__(self):
        return 'Score: {}'.format(self.name)
[...]

Die Models tragen wir ebenfalls in der admin.py ein:

# Erste Variante
# Register your models here.
myModels = [AuxProjects, AuxScore,AuxStatus,AuxTotalpaidin,StockBrokerage,StockCompany,StockTradingPlace,Currency,StockCountry,StockTradingFee, Unit,WindLocation,WindManufacturer,WindParc]
admin.site.register(myModels)

# zweite Variante
class StockTradingAdmin(admin.ModelAdmin):
    list_display = ('company','orderno','transaction_time')
    ordering = ('company__name','orderno','transaction_time')
    search_fields = ('company__name',)

admin.site.register(StockTrading, StockTradingAdmin)

Dabei gibt es 2 Varianten. Die erste ist die einfachste und Django übernimmt die Sortierung und die Beschriftung. Dazu tragt den Models Namen in der MyModels Liste ein. Falls ihr z.B. die angezeigten Informationen, die Sortierung und die Suche anpassen wollt, legt eine eigene Klasse für das Model an.

Anhand der Models wird das Admin Interface aufgebaut. Zurzeit sind dieses bei mir so aus:

 

Dadurch, das die Datentypen wie int, float, varchar etc. bei den Models mit angegeben werden, wird die Eingabe vereinfacht. Django sucht dabei schon die richtigen Eingabefelder aus. Hier ganz gut zu sehen bei dem Hinzufügen eines neuen Trades:

 

 

 

 

 

 

 

 

 

 

Projekt: Asset Management

Ich habe mich vor längerem dazu entschlossen, ein Teil meines Geldes zu investieren, um daraus später Profite zu erzielen. Dabei habe ich in die unterschiedlichsten Formen wie erneuerbare Energien, Aktien und P2P Netzwerke (z.B. Auxmoney) investiert. Jeder dieser Formen hat zumeist sein eigenes, ich nenne es mal Dashboard, wo der Verlauf begutachtet und der Profit aufgelistet wird. Daraus resultierend habe ich eine Menge Apps/Internetseiten, wo die jeweiligen Informationen liegen. Einige boten mir schon die, von mir verlangten Informationen, einige nicht. Alles in allem wollte ich eine zentrale Stelle. Ich entschloss mich dafür ein Datawarehouse aufzusetzen und bin mit dem OpenSource DWH Metabase fündig geworden. Metabase kann ich auf meinem vServer betreiben, was für mich Voraussetzung war. Die Installation bzw. das Starten des DWH’s ist relativ einfach – die geladene metabase.jar ausführen. Für diesen Prozess habe ich mir ein Script geschrieben, welches ihr unter dem Beitrag Metabase Startup Script finden könnt.

Backend

Das Backend besteht aus 2 MySql Datenbanken mit mehreren Tabellen. Um nicht alle Daten selbst in die Tabellen eintragen zu müssen, gibt es einige Scripte, die diese Arbeit für mich abnehmen. Unter anderem das Script aus dem Beitrag openweather. Auch benutze ich das Django Admin Interface, dazu aber mehr im zweiten Teil dieses Projektes.

Damit die Struktur einfacher zu verstehen ist, habe ich den Hintergrund jeder Datenbank Tabelle niedergeschrieben.

AUX_PROJECTS finanzierte Auxmoney Projekte
AUX_SCORE AuxmoneyScore zur Bewertung der Liquidität
AUX_TOTALPAIDIN Einzahlungen auf das Auxmoneykonto
AUX_STATUS Projektsstatus(verkauft,aktiv,etc.)
STOCK_TRADING_PLACE Handelsplatz(Tetra,direkt,etc.)
STOCK_TRADING_FEE Handelsplatzgebühren
STOCK_COMPANY Firmeninformationen
STOCK_COUNTRY Land der Firma
STOCK_BROKERAGE der Broker/Bank
STOCK_DIVIDENT Ausgezahlte Dividende
STOCK_AVAILABLE_STOCK zurzeit gehaltene Aktien
STOCK_TRADING gekaufte Aktien
STOCK_SOLD_STOCK verkaufte Aktien
WIND_DEPOSIT getätigte Einzahlung
WIND_INCOME erzieltes Einkommen
WIND_PARC Windpark Informationen
WIND_LOCATION Windpark Standorte
WIND_MANUFACTURER Windmühlenhersteller
WIND_TYPE Windmühlen-Typ
WIND_MILL Windmühlen Informationen
WIND_ENERGY_PERFORMANCE Windmühlen Performance
WEA_OPENWEATHER URL + apikey
WEA_CITY Standortcode von Openweather
CUR_WEATHER Wetterdaten von Openweather
Currency Währungen
Unit Einheiten

Tabellen mit dem Präfix AUX sind für meine Auxmoney Aktivitäten. WIND ist für die Windpark-Daten und WEA für die von openweather gezogenen Winddaten. Stock hat hier den größten Anteil und ist für meine Tätigkeiten an der Börse.

STOCK_SOLD_STOCK hat zusätzlich noch 2 Trigger. Einen vor und einen nach dem Insert. Vor jedem Insert in STOCK_SOLD_STOCK wird das Profit berechnet, welches ich mit dem Verkauf erwirtschaftet habe.

CREATE DEFINER=`user`@`%` TRIGGER `STOCK_SOLD_STOCK_before_insert` BEFORE INSERT ON `STOCK_SOLD_STOCK` FOR EACH ROW BEGIN

SET NEW.PROFIT = NEW.FINAL_PAYOUT - (SELECT NEW.QUANTITY * PRICE_AVG FROM STOCK_AVAILABLE_STOCK WHERE COMPANY = NEW.COMPANY);

END

Nach dem Insert wird die STOCK_AVAILABLE_STOCK mit der aktuellen Stückzahl geupdated.

CREATE DEFINER=`user`@`%` TRIGGER `STOCK_SOLD_STOCK_after_insert` AFTER INSERT ON `STOCK_SOLD_STOCK` FOR EACH ROW BEGIN

UPDATE STOCK_AVAILABLE_STOCK SET QUANTITY = QUANTITY - NEW.QUANTITY WHERE COMPANY = NEW.COMPANY;

END

Ähnliches habe ich auch für STOCK_TRADING. Hier wird die Stückzahl addiert und nicht subtrahiert. Falls es eine Firma ist, mit der ich noch nicht gehandelt habe, wird ein neuer Eintrag erstellt.

CREATE DEFINER=`user`@`%` TRIGGER `STOCK_TRADING_after_insert` AFTER INSERT ON `STOCK_TRADING` FOR EACH ROW BEGIN

INSERT INTO STOCK_AVAILABLE_STOCK(COMPANY,QUANTITY,PRICE_AVG,UPDATED_AT) 
  select * from (select NEW.COMPANY,0 as QUANTITY,0 as PRICE_AVG,now()) AS tmp where NOT EXISTS ( 
    select COMPANY from STOCK_AVAILABLE_STOCK where COMPANY = NEW.COMPANY
  ) LIMIT 1;

UPDATE STOCK_AVAILABLE_STOCK SET QUANTITY = QUANTITY + NEW.QUANTITY, PRICE_AVG = ((PRICE_AVG * (QUANTITY - NEW.QUANTITY)) + NEW.FINAL_PAYMENT) / (QUANTITY) WHERE COMPANY = NEW.COMPANY;

END

Durch diese Trigger habe ich immer einen Überblick über meine gehaltenen Aktien und deren Mittelwert, ohne dass ich diese immer wieder neu ermitteln muss. Erspart mir also eine Menge Arbeit.

Controlpanel

Damit ich nicht alle Informationen mit SQL Befehlen in die Datenbank schreiben muss, habe ich Django als Zwischenschicht eingebracht. Django stellt mir mit seinem Admin Interface große Dienste zur Verfügung. Den Django Beitrag zu diesem Projekt findet ihr hier: Projekt: Asset Management Teil2 Django

Raspberry Backup erstellen

Auch wenn der Raspberry Pi schnell aufgesetzt ist, so ist es immer ärgerlich wenn Daten verloren gehen. Hier besonders die Konfigurationen, die bei einem Ausfall der SD Karte verloren gehen könnten. Ein Backup schützt vor totalen Daten- und Konfigurationsverlust. Nun gibt es mehrere Lösungen ein Backup zu erstellen, ich benutze hier aber die Linux internen, die per default schon bei Raspbian dabei sind. in diesen Zusammenhang spreche ich von dem Linux-Tool dd(disk dump). dd kopiert Festplatten, Dateien und Partitionen „bit-genau“, heißt nichts anderes, als das der Datenträger Bit-für-Bit ausgelesen wird unabhängig von dessen Inhalt. Ebenfalls ignoriert es das Dateisystem auf dem Datenträger.

Schauen wir uns nun die Festplatte bzw. die Partitionen an, die wir sichern können.

sudo fdisk -l

Disk /dev/mmcblk0: 29,7 GiB, 31914983424 bytes, 62333952 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa92095ed

Device         Boot   Start      End  Sectors  Size Id Type
/dev/mmcblk0p1         2048  5033203  5031156  2,4G  e W95 FAT16 (LBA)
/dev/mmcblk0p2      5033204 62333951 57300748 27,3G  5 Extended
/dev/mmcblk0p5      5038080  5103613    65534   32M 83 Linux
/dev/mmcblk0p6      5103616  5627901   524286  256M  c W95 FAT32 (LBA)
/dev/mmcblk0p7      5627904 62333951 56706048   27G 83 Linux

Um die gesamte Festplatte samt Bootloader und Partitionen zu sichern, nehme ich das /dev/mmcblk0 Label für den „if“ Parameter. Dieser benötigt die Source, die gesichert werden soll. In „of“ wird der Backup-Destination angegeben. Bei mir ist das mein Nas, welches ich vorher gemountet habe. Ab Version 8.24 kann eine Statusanzeige aktiviert werden.

sudo mount -t cifs -o user=user,password=passwort,rw,file_mode=0777,dir_mode=0777 //nas/<pfad> /mnt/nas

BACKUP_PFAD= "/mnt/nas"
BACKUP_NAME= "raspbackup"
sudo dd if=/dev/mmcblk0 of=${BACKUP_PFAD}/${BACKUP_NAME}-$(date +%Y%m%d).img bs=1MB status=progress

Eine weitere Möglichkeit den Progress zu sehen, ist die Verwendung des USR1 Signals in einem zusätzlichen Terminal.

Dazu sucht ihr im neuen Terminal mit

ps -a

den dd Prozess heraus und kopiert die PID.

kill -USR1 <PID>

Nachdem ihr diesen Befehl abgesetzt habt, erscheint eine Fortschrittsanzeige in dem 1. Terminal, wo der dd-Prozess läuft. Mit watch könnt ihr den Befehl automatisch in Intervallen triggern lassen.

watch -n300 "sudo kill -USR1 <PID>"

Je nach Größe der SD-Karte, kann das Backup natürlich länger dauern.

Damit das Backup im Intervall erstellt wird, kann ein Cronjob erzeugt werden, der diese Tätigkeiten eigenständig ausführt.

Dafür erzeugen wir eine .sh, die die erforderlichen Befehle ausführt.

vi backup.sh

#!/bin/bash
BACKUP_PFAD= "/mnt/nas" 
BACKUP_NAME= "raspbackup" 
sudo mount -t cifs -o user=user,password=passwort,rw,file_mode=0777,dir_mode=0777 //nas/<pfad> /mnt/nas 
sudo dd if=/dev/mmcblk0 of=${BACKUP_PFAD}/${BACKUP_NAME}-$(date +%Y%m%d).img bs=1MB status=progress

exit 0

Gefolgt von dem Cronjob.

sudo crontab -e

* 2 * * * /etc/backup.sh

 

Der Cronjob wird nun jeden Tag um 2:00h aufgerufen und erstellt uns ein Backup.

rsync over ssh

Ich bin gerade dabei mich intensiv mit Deep Learning zu beschäftigen. Als Werkzeug dient mir dazu Anaconda und Jupyter Notebook. Dabei hatte ich die Idee, Jupyter Notebook auch dann noch zu benutzen, wenn ich gerade nicht zuhause bin. Wollte aber trotz alledem überall mit den gleichen Daten arbeiten. Von Zuhause würde ich auf das NAS zugreifen, welches nicht von Außen erreichbar ist und auch nicht soll. Wenn ich unterwegs bin, muss ich also zwangsweise einen anderen Weg einschlagen.  Hier kommt mein vServer zum Einsatz, den ich als Cloud hinter einem VPN benutze. Dort läuft ebenfalls Anaconda mit Jupyter Notebook(Ich könnte zwar dauernd einen VPN Tunnel aufbauen, auch wenn ich Zuhause bin, aber das möchte ich nicht). Die technische Infrastruktur wäre also vorhanden, aber wie schaffe ich es, dass die beiden Sourcen in-sync bleiben und dass ohne mein Beisein oder das ich etwas beisteuere? Durch die offenen Ports auf dem vServer wird es noch erschwert, denn nur der VPN Port und der SSH Port sind nach Außen hin offen. Also kann ich ebenfalls nur einen Weg ansteuern der per SSH möglich ist. Getriggert wird das alles von meinem NAS, da der vServer keine Verbindung zu meinem NAS aufbauen kann. Die Gründe hatte ich weiter oben schon genannt. Das alles in ein Shell Script verpackt und auf dem NAS mittels cronjob periodisch ausführen lassen. SSH fragt allerdings per Default das Password ab, das müsste dann mit einem privaten, öffentlichen Schlüssel Verbund umgangen werden.

Zusammengefasst:

  • NAS triggert die Synchronisation
  • Synchronisation über SSH
  • SSH ohne Password Abfrage
  • Shell Script mittels Cronjob periodisch ausführen

Fangen wir also an. Zuerst erstellen wir den privaten/öffentlichen Schlüssel. Wie das geht, habe ich in dem Artikel „SSH Verbindung mit privaten/öffentlichen Schlüssel“ beschrieben. Ich gehe jetzt also davon aus, dass die SSH Verbindung ohne Passwort soweit funktioniert.
Es fehlt also nur noch das Script und der Cronjob. SCP wäre die einfachste Lösung. Jedoch wäre der Overload zu hoch und ich müsste eigene Funktionen einbauen, die zwischen Dateien und Ordner unterscheiden, sowie auf welcher Seite gerade die neuste Datei liegt und ob neue dazugekommen sind. Das wäre ziemlich mühselig. Daher habe ich was anderes gesucht und bin mit rsync fündig geworden. Rsync bietet solche Funktionen und lässt sich über SSH tunneln. Das Script besteht nun aus einfachen 5 Zeilen.

 

echo "`date +%d.%m.%Y\ %T` `basename $0` (pid: $$): NAS->CLOUD" >> /share/homes/user/Programing/anaconda/rsync.log
rsync -ruzv --size-only -e "ssh -p <sshport>" /share/homes/user/Programing/anaconda/notebooks/* user@vserver:~/Projekte/JNotebook/. >> /share/homes/user/Programing/anaconda/rsync.log
echo "---------------------------------------------" >> /share/homes/user/Programing/anaconda/rsync.log
echo "`date +%d.%m.%Y\ %T` `basename $0` (pid: $$): CLOUD->NAS" >> /share/homes/user/Programing/anaconda/rsync.log
rsync -ruzv --size-only -e "ssh -p <sshport>" root@vserver:~/Projekte/JNotebook/* /share/homes/user/Programing/anaconda/notebooks/. >> /share/homes/user/Programing/anaconda/rsync.log

 

Am wichtigsten sind hier natürlich die Schalter: -ruzv, –size-only und -e.

r = recurse into directories
u = skip files that are newer on the receiver
z = compress file data during the transfer
v = increase verbosity
e = specify the remote shell to use
size-only = skip files that match in size

Mit dem Schalter -e „ssh -p <sshport>“ wird der SSH Tunnel zum vServer aufgebaut. Als sshport tragt ihr den Port ein, auf dem euer Server auf SSH lauscht. Üblich auf dem Port 22.
Rsync führe ich in beiden Richtungen aus, also vom NAS -> CLOUD und CLOUD -> NAS, damit sich die Datenstände abgleichen.

den cronjob richten wir mit

crontab -e

ein und tragen die nachfolgende Zeile ein.

# m h dom m dow cmd
*/10 * * * * /etc/rsync-anaconda.sh

Diese Zeile bedeutet, dass das Script jede 10 Minuten aufgerufen wird. Die Ergebnisse der Ausführung wird soweit in eine Log Datei auf meinem NAS niedergeschrieben. Somit habe ich auch alles im Blick, falls etwas schief läuft.

 

 

 

icinga2 auf dem Raspberry Pi mit Postgresql

Icinga2 und Icingaweb2 installieren

Damit wir Icinga2 und Icingaweb2 installieren können, benötigen wir das entsprechende Repository. Sobald dies eingerichtet ist, installiert es mit dem apt Befehl.

cuurl https://packages.icinga.com/icinga.key | sudo apt-key add - echo "deb http://packages.icinga.com/raspbian icinga-stretch main" \ | sudo tee /etc/apt/sources.list.d/icinga.list
sudo apt update
sudo apt install icinga2 icingaweb2
http://localhost/icingaweb2

 

Nachdem wir auf die Seite gelangt sind, verlangt Icinga ein Token zur Authentifizierung von uns. Dem wollen wir nun nachgehen.

sudo icingacli setup config directory --group icingaweb2;
sudo icingacli setup token create;

 

Optional könnten wir noch überprüfen, ob die erforderlichen Gruppen für Icinga existieren. Es sollte ungefähr wie unten aussehen. Die icingaweb2 Gruppe muss existieren und die Gruppe www-data enthalten.

cat /etc/groups |grep -i incinga

icingaweb2:x:117:www-data

 

Können wir, dank der erfolgreichen Authentifizierung, weiter auf der Seite gehen, so gelangen wir zur Auswahl der Module. Wählt euch eure entsprechenden Module aus.

Auf der nächsten Seite überprüft Icinga die System-Abhängigkeiten.

Bevor wir weitergehen, sollten wir diese Fehler lösen. Als erstes richten wir die korrekte Zeitzone ein. Mit einem Editor wie vi könnt ihr die date.timezone entkommentieren und eure entsprechende Zeitzone setzen. Bei mir ist es Europe/Berlin.

sudo vi /etc/php/7.3/apache2/php.ini


cat /etc/php/7.3/apache2/php.ini | grep date.timezone
; http://php.net/date.timezone
date.timezone = Europe/Berlin

Gefolgt von der Installation der fehlenden PHP Module.

sudo apt-get install php-pgsql 
sudo systemctl restart apache2

Es sollten nun alle Fehler und Warnungen beseitigt sein.

Auf der nächsten Seite wird nun die Authentifizierungsmethode abgefragt. Ich habe mich für die Datenbank entschieden.

Die Einstellungen für die Datenbank müssen wir nun setzen.  Icinga richtet die Datenbank und den User selber ein. Ihr müsst also nur einen User angeben, der die Rechte hat Datenbanken und User anzulegen.

Danach kommt eine Abfragen bzgl. des Authentifizierungs-Backend, die ihr weiterklicken könnt.

 

Im darauffolgenden Schritt richten wir den ersten Benutzer für die Icinga Weboberfläche ein. Die Eingabe ist dabei euch überlassen.

Die Konfiguration vom Logging kann soweit, wie von Icinga vorgegeben, verwendet werden.

Die Einrichtung von Icingaweb2 wäre nun abgeschlossen und es folgt eine Übersicht der getätigten Konfigurationen.

 

Icinga2-IDO

Kommen wir nun zur Einrichtung von Icinga2-IDO.

Vorweg möchte ich sagen, dass wir für die IDO eine Datenbank einrichten müssen. Wie ihr das macht habe ich unter „Postgres Datenbank installieren und einrichten“ beschrieben. Da die IDO Einrichtung nichts weiteres verlangt, mache ich hier mit der Visualisierung weiter.

 

Sobald die Einrichtung abgeschlossen ist, könnt ihr die Verbindung validieren.

„Es gibt zurzeit keine Icinga-Instanz die in die IDO schreibt. Stelle sicher, dass eine Icinga-Instanz konfiguriert ist und in die IDO schreiben kann“ – Falls ihr diese Meldung bekommt, dann solltet ihr unbedingt /etc/icinga2/features-enabled/ido-pgsql.conf überprüfen. Dort sollten die Einstellungen deckungsgleich mit denen sein, die ihr vorher in der Web-Konfiguration festgelegt habt.

 

/**
 * The db_ido_pgsql library implements IDO functionality
 * for PostgreSQL.
 */

library "db_ido_pgsql"

object IdoPgsqlConnection "ido-pgsql" {
  user = "user",
  password = "password",
  host = "localhost",
  database = "icinga_ido"
}

 

 

Postgres Datenbank installieren und einrichten

Zuallererst müssen natürlich die erforderlichen Pakete installiert werden.

sudo apt install postgresql 
sudo apt install icinga2-ido-pgsql

Für Icinga IDO müssen wir diesmal eine eigene Datenbank erzeugen. Dafür melden wir uns mit dem postgres User in Linux an

sudo -u postgres -i

Erstellen wir nun den User und die Datenbank

postgres@raspberrypi:~$ createuser --interactive -P
Geben Sie den Namen der neuen Rolle ein: user
Geben Sie das Passwort der neuen Rolle ein:
Geben Sie es noch einmal ein:
Soll die neue Rolle ein Superuser sein? (j/n) n
Soll die neue Rolle Datenbanken erzeugen dürfen? (j/n) n
Soll die neue Rolle weitere neue Rollen erzeugen dürfen? (j/n) n
postgres@raspberrypi:~$ createdb -O user icinga_ido
psql -d icinga_ido -f /usr/share/icinga2-ido-pgsql/schema/pgsql.sql

Noch ein letzter Kontrollblick, ob alles korrekt erstellt worden ist. Dafür verwenden wir den psql Befehl und im weiteren \du für die Liste der Rollen(User) bzw. \l für die Liste der Datenbanken.

psql
postgres=# \du
                                      Liste der Rollen
 Rollenname |                            Attribute                            | Mitglied von
------------+-----------------------------------------------------------------+--------------
 postgres   | Superuser, Rolle erzeugen, DB erzeugen, Replikation, Bypass RLS | {}
 user       | Passwort gültig bis infinity                                    | {}

postgres=# \l
                                  Liste der Datenbanken
    Name    | Eigentümer | Kodierung | Sortierfolge | Zeichentyp  |  Zugriffsprivilegien
------------+------------+-----------+--------------+-------------+-----------------------
 icinga_ido | user       | UTF8      | de_DE.UTF-8  | de_DE.UTF-8 | =Tc/user             +
            |            |           |              |             | user=CTc/user
 postgres   | postgres   | UTF8      | de_DE.UTF-8  | de_DE.UTF-8 |
 template0  | postgres   | UTF8      | de_DE.UTF-8  | de_DE.UTF-8 | =c/postgres          +
            |            |           |              |             | postgres=CTc/postgres
 template1  | postgres   | UTF8      | de_DE.UTF-8  | de_DE.UTF-8 | =c/postgres          +
            |            |           |              |             | postgres=CTc/postgres
(4 Zeilen)

 

 

Icinga2 API einrichten

Da ich die Befehle mittels API an die Monitoring-Instanz entsenden möchte, muss ich die Icinga2 API installieren.

root@raspberrypi:/home/pi# icinga2 api setup
information/cli: Generating new CA.
information/base: Writing private key to '/var/lib/icinga2/ca//ca.key'.
information/base: Writing X509 certificate to '/var/lib/icinga2/ca//ca.crt'.
information/cli: Generating new CSR in '/var/lib/icinga2/certs//raspberrypi.csr'.
information/base: Writing private key to '/var/lib/icinga2/certs//raspberrypi.key'.
information/base: Writing certificate signing request to '/var/lib/icinga2/certs//raspberrypi.csr'.
information/cli: Signing CSR with CA and writing certificate to '/var/lib/icinga2/certs//raspberrypi.crt'.
information/pki: Writing certificate to file '/var/lib/icinga2/certs//raspberrypi.crt'.
information/cli: Copying CA certificate to '/var/lib/icinga2/certs//ca.crt'.
information/cli: Adding new ApiUser 'root' in '/etc/icinga2/conf.d/api-users.conf'.
information/cli: Enabling the 'api' feature.
Enabling feature api. Make sure to restart Icinga 2 for these changes to take effect.
information/cli: Updating 'NodeName' constant in '/etc/icinga2/constants.conf'.
information/cli: Created backup file '/etc/icinga2/constants.conf.orig'.
information/cli: Updating 'ZoneName' constant in '/etc/icinga2/constants.conf'.
information/cli: Backup file '/etc/icinga2/constants.conf.orig' already exists. Skipping backup

 

Richten wir nun einen neuen API-User ein und fahren mit dem Websetup fort:

vi /etc/icinga2/conf.d/api-users.conf

object ApiUser "user" {
  password = "password"
  // client_cn = ""

  permissions = [ "*" ]
}

Diesen gerade erstellten Login tragen wir in das Websetup ein.

Falls alles soweit geklappt hat, können wir mittels curl die API testen. Icinga sollte mit einem kleinem Response antworten.

curl -k -s -u icinga:password 'https://localhost:5665/v1' 
<html><head><title>Icinga 2</title></head><h1>Hello from Icinga 2 (Version: r2.10.5-1)!</h1><p>You are authenticated as <b>user</b>. Your user has the following permissions:</p> <ul><li>*</li></ul><p>More information about API requests is available in the <a href="https://docs.icinga.com/icinga2/latest" target="_blank">documentation</a>.</p></html>

 

Das Setup wäre nun abgeschlossen. Icinga ist lauffähig, hat bislang aber nur den PI als überwachten Host. Wie die Objektpflege über die API passiert, erkläre ich in einem neuen Beitrag, damit dieser nicht zu lange wird :).

 

Gutes gelingen!