Pirozeda

Aus Hobbyelektronik.org
Fertig aufgebauter Pirozeda (Foto von Hans)

"Geht das auch direkt mit dem Raspberry Pi?" War eine Anfrage, die ich nun schon ein paar mal bekommen habe.

Es besteht also Interesse.

Raspberry Pi als SPI-Slave

Bitbanging

Vom Prozeda-Decoder weiß ich, dass der SPI mit etwa 250 kHz läuft, was nicht allzu viel ist. Also war mein erster Ansatz Bitbanging. Die Daten sind mit steigender Flanke der Clock-Leitung gültig - also schreibe kopiere ich mir erst einmal ein Testprogramm in Python zusammen, das einen Interrupt aktiviert, die Frequenz misst und auf der Konsole ausgibt:

#!/usr/bin/env python2.7
# original script by Alex Eames http://RasPi.tv

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
count = 0

def my_callback(channel):
    global count
    count += 1

GPIO.add_event_detect(23, GPIO.RISING, callback=my_callback)

try:
    while True:
        time.sleep(1)
        print "%i Hz" % count
        count = 0


except KeyboardInterrupt:
    GPIO.cleanup()       # clean up GPIO on CTRL+C exit

GPIO.cleanup()           # clean up GPIO on normal exit

Auf der anderen Seite hängt ein Logic 8, der zum Signalgenerator umfunktioniert wurde. Bis IIRC ca. 80 kHz funktioniert die Messung, danach gibt es aliasing. Gleichzeitig steigt die CPU-Last bei den höheren Frequenzen auf 67 %. Nicht wirklich was ich mir vorgestellt habe.

In C habe ich es nicht ausprobiert, bin mir aber ziemlich sicher, dass dort die Schwuppdizität deutlich besser ist.

Hardware-SPI

Streng genommen stellt der Raspberry nur einen Hardware-SPI-Master an seinem Pinheader zur Verfügung, allerdings lässt sich dessen PCM-Port als SPI-Slave missbrauchen (danke an Hans für den Hinweis).

Der erste Satz auf der Projektseite macht allerdings nicht viel Mut: "[...] even though it has some serious limitations". Kurz: es gibt keinen Chip-Select, den muss man manuell implementieren. Müsste aufgrund der etwas verschwurbelten Kommunikation sowieso gemacht werden. Chip sieht während der Kommunikation maximal 500 Hz, was ebenfalls problemlos möglich sein sollte.

Auch wenn das alles verlockend klingt: Ich kneife. In Sachen C unter Linux bin ich überhaupt nicht fit - um es wirklich sauber zu implementieren wäre zudem sicher ein Kernel-Modul fällig.

Damit wäre auch die eingangs gestellte Frage beantwortet: Nein, aktuell nicht.

Adapter

Allerdings gibt es Mikrocontroller-Software, die funktioniert.

Fehlt nur noch eine zum Raspberry passende Hardware.

Hardware

Mini-DIN-Buchse, Optokoppler, Mikrocontroller und Buchsenleiste reichten als Minimalbeschaltung für den Raspi. Ok, noch ein bisschen Außenbeschaltung, aber im Großen und Ganzen ist das alles. Die Stromversorgung erfolgt durch den Prozeda-Regler; durch den Optokoppler gibt es keine Wechselwirkung zwischen Regler und Pi (falls eine Seite nicht mit Strom versorgt wird).

Mit dem Raspberry Pi Zero als Referenz sind die Bauteile so platziert, dass die Mini-DIN-Buchse als Verlängerung der langen Kante des Pi agiert:

Die BOM ist dazu überschaubar:

Menge Referenz Wert Package Reichelt Bestellcode
1 SV3 FE07-2 RND 205-00656
1 SV2 MA03-2 SL 2X40G 2,54
2 C1, C5 100n C0603 X7R-G0603 100N
1 R1 10k R0603 RND 0603 1 10K
2 C3, C4 10p C0603 NPO-G0603 10P
1 Q3 12M MJ 12,000000-MJ
3 R14, R15, R16 1k R0603 RND 0603 1 1,0K
1 C15 1n C0603 X7R-G0603 1,0N
1 R17 2k2 R0603 RND 0603 1 2,2K
1 C2 2u2 C0805 KEM X5R0805 2,2U
1 R13 470 R0603 RND 0603 1 470
1 OK2 6N137 DIL08 6N 137
1 Q1 BSS138 SOT23 BSS 138 SMD
2 U$2, U$3 DUMMY DUMMY SE-DIO M08
1 IC3 MEGA88/168-AU TQFP32-08 ATMEGA 88PA-AU
1 X3 MINIDIN8 MINI-DIN8 EB-DIOS M08
3 C7, C11, C14 na C0603 NPO-G0603 220P

Im ersten Aufbau wurde ein ATmega88A verwendet, generell sollte auch die nicht-A-, P- oder PA-Variante sowie die verschiedenen Größen (8 bis 32 KB Flash) problemlos funktionieren. Für den ATmega48xx braucht's noch ein bisschen RAM-Optimierung.

Was ich jedoch nicht bedacht habe: Für den Raspberry Pi 3 ist die Anordnung in mehrerlei Hinsicht schlecht:

  • die WLAN-Antenne wird vom Blechdeckel der Mini-DIN-Buchse verdeckt
  • die Leiterkarte passt so überhaupt nicht ins Gehäuse (kollidiert so mit der Wand, dass man ein großes Stück heraustrennen müsste)
  • Pads für den Optokoppler kollidieren mit dem (optionalen) Kühlkörper des Prozessors

Aber hey, der Adapter funktioniert im ersten Anlauf.

Um die oben genannten Nachteile zu umgehen habe ich die Buchsenleiste entfernt und durch eine Stiftleiste auf der anderen Seite der Leiterkarte ersetzt. So kann der Adapter mit einem Flachbandkabel mit dem Pi verbunden werden. Der Pinheader für ISP ist absichtlich so platziert, dass der Pfostenverbinder nicht versehentlich auf ihn gesteckt werden kann:

Ein weiterer - eher dümmlicher Layoutfehler - ist die Platzierung von C2. Ich habe die Leiterkarte zu oft im Kopf gedreht und das Bauteil ist genau auf der Kante der Mini-DIN-Buchse gelandet. Da die Kondensatoren der Tiefpässe nicht platziert sind, konnte er einfach zwischen Pin 5 der Buchse und Pin 2 von C14 eingelötet werden. Um Kurzschlüsse zu vermeiden hilft ein Stück Kapton-Tape an Stelle des C2.

Als Verbindung zwischen Solarregler und Adapter dient ein Straight-Through-Kabel, das ich selbst zusammengelötet habe. Obwohl nicht alle Pins benötigt werden (Pin 4 (GND), 5 (3,3 V), 6 (MOSI), 7 (SCK) und 8 (!CS)), ist es eine sehr fummelige Angelegenheit - fertig konfektionierte Leitungen konnte ich nicht finden. Um zumindest nur einen Stecker löten zu müssen, kann die Seite in Richtung Adapter direkt auf die Leiterkarte gelötet werden. Für eine grundlegende Zugentlastung kann ein Kabelbinder durch die Befestigungslöcher geführt werden.

Eine Leitungslänge von 2 m stellte in meinen Tests kein Problem dar, mit großer Wahrscheinlichkeit geht da auch ein gutes Stück mehr.

Um das Löten der Leitung gänzlich zu vermeiden, merkte Frank (vom Original-Artikel) an, dass es wahrscheinlich geschickter wäre, den Adapter wie den Loggingstick aufzubauen, also die Leiterkarte direkt an den Mini-DIN-Stecker zu löten. Da zwangsläufig eine Leitung wegführt und der Schirm und die Plastikhülle drüber sollte (auch wenn er im Regler nicht angebunden ist), war mir das allerdings zu wackelig.

Hans, der die erste Hardware bekommen hat, hat sich noch etwas mehr Arbeit gemacht und dem Ganzen das ModMyPi-Gehäuse (RPi 2/3 Case) mit Spacer und Abdeckung der USB-Ports und SD-Karte verpasst:

Firmware

AVR
Typ ATmega88PA
Takt 12 MHz
Fuses
High 0xC5
Low 0xDE
Extended 0x07
Engbedded com logo.png Details

Die Originalfirmware kann nach Anpassung des Targets direkt verwendet werden, allerdings habe ich mich dafür entschieden, diese etwas zu anzupassen. Der AVR gibt die Pakete nun als Hex-String aus. Dadurch ist die Firmware gegenüber der Prozeda-Hardware so agnostisch wie möglich.

Um Schluckauf zu vermeiden, werkelt der Watchdog mit relativ langem Timeout (2 Sekunden). Grund hierfür ist, dass er nur bei einer erfolgreich empfangenen Nachricht zurückgesetzt wird.

Beim Flashen der Firmware muss unbedingt darauf geachtet werden, dass die Verbindung zum Solarregler getrennt wird, da sowohl SPI als auch ISP die gleichen Pins verwenden und es trotz der Serienwiderstände zu irreparablen Schäden kommen kann.

Nach dem Verbinden mit dem Regler erfolgt beispielsweise folgende Ausgabe:

Built on: Oct 30 2017 16:14:56
Display:20204b6f6c6c656b746f722020202020202020202020202020323220052d54203232203233200101180000000000000000000000000000000000000000000004
Display:20204b6f6c6c656b746f722020202020202020202020202020323220052d54203232203233200101180000000000000000000000000000000000000000000004
Display:20204b6f6c6c656b746f722020202020202020202020202020323220052d54203232203233200101180000000000000100000000000000000000000000000005
Logdata:200071008a013000e300e200fc08fc08fc080600fc08fc08e60000000000b4ff0000000000000000000000000000000000000000000000000000000001000200000120e8
Display:20204b6f6c6c656b746f722020202020202020202020202020323220052d54203232203233200101180000000000000100000000000000000000000000000005

Die Konfiguration des UART ist 115200 Baud 8N1, Zeilenenden sind CR+LF (\r\n).

Neben den oben gezeigten Ausgaben kann auch die Meldung "rxErr" gesendet werden, falls eine Nachricht nicht richtig erkannt wurde.

Zusätzlich gibt die Firmware die ersten 200 Header-Zeilen nach dem Start des Mikrocontrollers aus, was die Erstellung der Konfiguration erleichtert.

Download/Update der Firmware

Hier liegen mittlerweile 3 dedizierte AVR-Programmer herum. Was aber, wenn man nur den Raspberry hat?

AVRDUDE hat mittlerweile Unterstützung für SPI-Module unter Linux. Eine ausführliche Anleitung findet sich auf mikrocontroller.net

Die Verbindung ist wie folgt herzustellen:

Signalname Adapter SV2 Pin Raspberry GPIO Raspberry Pin Farbe im Bild
MISO 1 BCM 9 21 Grün
3V3 2 3V3 17 Rot
SCK 3 BCM 11 23 Violett
MOSI 4 BCM 10 19 Blau
!RES 5 BCM 25 22 Weiß
GND 6 GND 20 Schwarz

Zur Veranschaulichung noch ein Bild anhand eines Raspberry Pi Zero und einer Mockup-Leiterkarte:

Richtig verbunden und mit (wichtig!) abgestecktem Regler lässt sich mit folgende Befehle der AVR fusen & flashen:

sudo /usr/local/bin/avrdude -c linuxspi -p m88p -P /dev/spidev0.0 -U lfuse:w:0xDE:m -U hfuse:w:0xC5:m -U efuse:w:0x07:m
sudo /usr/local/bin/avrdude -c linuxspi -p m88p -P /dev/spidev0.0 -U flash:w:"ProzedaReader.hex"

AVRDUDE bietet keine explizite Unterstützung für den ATmega88PA. Das ist allerdings auch nicht nötig, da er bis auf kleine Unterschiede funktional dem ATmega88P entspricht.

Pirozeda-HAT

Um den Artikel übersichtlicher zu halten, gibt es einen gesonderten Arikel für den Pirozeda-HAT

Software auf dem Raspberry

Vorbereitung

Um den Adapter nutzen zu können, muss zunächst die Konsole auf dem UART deaktiviert werden. Das geht entweder über sudo raspi-config (keine Konsole aber UART aktiv) oder in der Datei boot/cmdline.txt - in ihr muss der Eintrag console=serial0,115200 entfernt werden - die Änderung zieht erst nach einem Neustart.

Vor Pirozeda 0.4

Anschließend ein paar Module für Python - damit es einfach geht vorab (falls noch nicht vorhanden) - pip installieren:

sudo apt-get install python-pip
sudo python -m pip install pyserial
sudo python -m pip install python-jsonrpc

Nun kann mit miniterm getestet werden, ob was durchkommt:

sudo python -m serial.tools.miniterm /dev/serial0 115200

Es sollten mehrere Nachrichten innerhalb einer Sekunde angezeigt werden. Ist man fertig, kann das Terminal (zumindest bei deutschem Tastatur-Layout) mit Strg+5 beendet werden.

Ab Pirozeda 0.4

Pirozeda ab 0.4 verwendet Python 3.

Dementsprechend müssen die Module für die neuere Plattform installiert werden. Für JSON-RPC wird nun eine andere Lib verwendet, die zugleich "Werkzeug" als Webserver verwendet:

sudo python3 -m pip install json-rpc werkzeug pyserial

Auch hier kann über miniterm geprüft werden, ob Daten ankommen.

Backend

Auch wenn die Daten vorverarbeitet vom Mikrocontroller kommen, lässt sich mit ihnen noch nicht allzu viel anfangen.

Deshalb muss ein Programm her, das sie entgegennimmt, weiter verarbeitet und dem Nutzer zur Verfügung stellt. Zur Umsetzung dessen habe ich Python 2.7 verwendet.

Das Programm liest die Daten über die Klasse ProzedaReader ein und stellt sie über verschiedene Events bzw. Puffer bereit. Über die Klassen ProzedaLogdata und ProzedaDisplaydata können die Daten (unter der Verwendung der zum eingesetzten Regler gehörenden Konfiguration) decodiert werden.

pirozeda.py ist das Hauptprogramm und übernimmt das in den Settings konfigurierbare Logging und die Bereitstellung der Daten über die JSON-Schnittstelle.

ProzedaReader

Der Prozedareader liest über einen Thread die Daten von der seriellen Schnittstelle ein und wandelt diese in die entsprechenden Objekte (ProzedaLogdata und ProzedaDisplaydata) um und ruft folgende Callbacks auf:

  • evt_rawdata_received(self, timestamp, line)
  • evt_logdata_received(self, entry)
  • evt_displaydata_received(self, entry)
  • evt_error_received(self, timestamp, errortype)

Ihnen können Methoden zugewiesen werden, die beim entsprechenden Ereignis aufgerufen werden. Aber Achtung: der Aufruf findet innerhalb des serialreader-Threads statt.

Gleichzeitig werden standardmäßig die letzten 10 Log- und Displaydata-Nachrichten im Cache gehalten.

Die Klasse ProzedaLogdata ist der Container für Logdaten, die sie beim instanziieren nur in das neue Objekt übernimmt. Das Parsen muss manuell durchgeführt werden, da es für das reine Logging nicht benötigt wird und sonst unnötig Ressourcen belegt werden würden. Wichtig und ziemlich unschön: bevor Daten geparst werden können, muss die Spaltenkonfiguration mit der statischen Methode set_config gesetzt werden. Das macht die Klasse undynamisch, allerdings ist die Wahrscheinlichkeit, dass man mehrere Systeme mit einer Anwendungsinstanz abdeckt eher gering.

Als es mit der Zielhardware (Sungo SXL - ohne plus) in Betrieb genommen wurde fiel auf, dass sich die vom Regler gesendeten Datensätze zur Implementierung (Sungo SXLplus) unterscheiden.

Das Gute ist, dass aufgrund der Agnostik des Adapters der Code bequem in Python angepasst werden kann. Dadurch dass die Daten roh geloggt werden (siehe unten) kann die Zuordnung des richtigen Parsers auch später nahezu ohne Informationsverlust erfolgen.

Prinzipiell kann die Erkennung der Spaltenzuordnung auch komplett autonom erfolgen (die entsprechenden Daten können von der Hardware ausgegeben werden). Mir war der Nutzen dafür jedoch zu klein, da es sich dabei um eine einmalige Einrichtung handelt und die Zuordnung der Spalten bzw. deren Benamung und Anzeige-Einstellung je nach System unterschiedlich sein können.

Pirozeda

Das Pirozeda-Backend übernimmt das dateibasierte Logging und bietet eine JSON-RPC-Schnittstelle um z. B. aktuelle Daten abzurufen.

Beim Logging habe ich mich bewusst gegen eine Datenbank entschieden, da die Implementierung ursprünglich als Demo und Einstiegspunkt für weitere Entwicklungen dienen sollte. In der aktuellen Konfiguration wird für jeden Tag eine Datei angelegt und alle 5-Minuten (einstellbar) ein Datensatz geschrieben.

Das Dateiformat ist einfach gehalten. Der Dateiname entspricht dem Format "prozeda_YYYY-MM-DD.txt" und der Inhalt der Syntax Timestamp\tEntrytype\tEntrydata

Aktuell werden drei Entrytypes verwendet - i (Info), d (Data) und w (Warning):

1509546700.738	i	Log opened
1509546760.524	d	IABxAJcBDwDjAOQA/Aj8CPwIBgD8CPwI6AAAAAAAtP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQACAAABINg=
1509546820.529	d	IABxAJgBDwDkAOQA/Aj8CPwIBgD8CPwI6AAAAAAAtP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQACAAABINo=
1509546880.534	d	IABxAJkBDwDkAOQA/Aj8CPwIBgD8CPwI6AAAAAAAtP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQACAAABINs=

Ein scharfer Blick verrät: die Daten sind Base64-kodiert. Es geht effizienter, es geht schöner, aber wie bereits geschrieben: Einstiegspunkt.

JSON-RPC

Zur Schonung des Speichermediums wird nicht nach jedem Satz ein Flush (Schreiben ins Dateisystem) durchgeführt sondern dem Filewriter bzw. dem Betriebssystem überlassen. Um dennoch auf aktuelle Daten zugreifen zu können, werden die Datensätze der letzten 24 Stunden im Minutentakt (ebenfalls einstellbar) im RAM gehalten und stehen per JSON-RPC zur Verfügung.

Darüber hinaus bietet das JSON-Interface bereits die wichtigsten Funktionen, um die erfassten Daten darstellen zu können. Diese sind in der Klasse JsonrpcRequestHandler beschrieben.

Befehl Beschreibung
info Ausgabe der Version des Backends
settings_get Einstellungen des Backends (siehe pirozeda_settings.py)
system_stats System-Statistik, darunter Speicherbelegung, Info über den Zustand des Readers und des Tracings
current_logentry Gibt den aktuellen Logentry zurück
columnheader_get Gibt die Kopfzeilen der vom Regler kommenden Daten zurück
ramlog_getlast Gibt die letzten Einträge aus dem RAM-Log zurück
fslog_flush Leert den Schreibpuffer für das Dateisystem-Log. Damit werden alle erfasten Daten auf die "Platte" geschrieben
fslog_read Daten aus den Dateisystem-Logs lesen. Vor dem Lesen wird ein Flush durchgeführt, um wirklich alle Daten zu bekommen
display_getcurrent Gibt die aktuellen Displaydaten zurück
trace_start Startet das Backend-Trace für die vorgegebene Zeit.
trace_stop Stoppt das Backend-Trace sofort (anstatt auf das Ablaufen des Timers zu warten)
trace_live Gibt Livedaten der UART-Schnittstelle aus

Frontend

Die Daten werden geloggt und bereitgestellt, aber noch nicht benutzerfreundlich dargestellt. Das muss das Frontend übernehmen:

Webserver

Als Webserver dient lighttpd mit PHP. PHP? Ja, PHP ("aber PHP!!11!eins" höre ich jetzt manche schreien - ja, ich weiß, das macht's nicht unbedingt besser). Natürlich kann auch Python - entweder direkt (nicht empfohlen) oder als weitere Instanz - als Webserver bzw. Sprache zum Abrufen der Daten verwendet werden.

Die Grundidee zur Trennung von Backend und Server ist, die Angriffspunkte von außen zu verringern.

Zur Einrichtung des Servers muss in der Shell folgendes eingegeben werden:

sudo apt-get install lighttpd

sudo groupadd www-data
sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 775 /var/www/html
sudo usermod -G www-data -a pi

sudo apt-get install php-common php-cgi php

sudo lighty-enable-mod fastcgi-php

Aus nicht ganz erfindlichen Gründen hat lighttpd index.htm nicht in der Suche für Index-Seiten, also kann/sollte die Konfiguration angepasst werden:

sudo nano /etc/lighttpd/lighttpd.conf

...und die Zeile beginnend mit index-file.names durch folgende ersetzt (oder angepasst) werden:

index-file.names            = ( "index.php", "index.htm", "index.html" )

Damit die Konfiguration gezogen wird, muss der Server neu gestartet werden:

sudo service lighttpd force-reload

Anwendung

Eigentlich allenfalls als kleine Demo beabsichtigt, ist aus der Anwendung mehr geworden als ursprünglich gedacht.

Bilder sagen hier mehr als Worte - angemerkt sei, dass mangels Regler vorab aufgezeichnete und schneller wiedergegebene Logdaten verwendet wurden, weswegen ein die Diagramme etwas gedrängt aussehen und die Displaydaten nicht angezeigt werden (wurden nicht aufgezeichnet).

Zum Plotten dient plotly.js, die Symbole im Display stammen von Font Awesome.

Das HTML-Dokument dient lediglich als Grundgerüst, die meisten Elemente werden per Javascript angelegt. Dreh- und Angelpunkt hierfür ist pirozeda.js. Um Standardaufgaben im DOM zu verkürzen, kommt d.js zum Einsatz. Durch die starke Verschachtelbarkeit der Befehle leidet allerdings die Lesbarkeit - das nur als Warnung.

Nach dem Laden der Seite wird ein SolarManager-Objekt angelegt, dieses fragt zunächst die Einstellungen des Systems ab, bevor die restlichen Elemente generiert werden.

Die Kommunikation mit dem Backend erfolgt über mehrere PHP-Dateien, die mehr oder weniger als Proxy agieren. Schließlich läuft das Backend (nicht ganz schön und sicher vermeidbar) als root und könnte somit größeres Unheil anrichten. Zwar könnte von der Seite auch direkt auf den JSON-RPC zugegriffen werden, was allerdings das Öffnen eines weiteren Ports (wenn man es nach draußen überhaupt freigeben will) erfordert. Gleichzeitig muss der Port auch von der Gegenseite erreichbar sein. In öffentlichen WLANs sind oft nur die Standardports zugelassen, um unbekannte Dienste zu sperren.

Sobald die Einstellungen geladen sind, wird die Seite initialisiert, heißt: Erstellen der Elemente und befüllen der Daten (nachdem diese geladen wurden).

Für den Plot werden die aktuellen und historischen Daten parallel verwaltet. Damit können die aktuellen im Hintergrund weiter aktualisiert bzw. erweitert werden, während historische Datensätze angezeigt werden.

Der zweite Tab beinhaltet den aktuelle Displayinhalt und die aktuellen Messwerte vom Regler. Dies läuft über zwei parallele Requests, wobei Displaydaten nur übertragen werden, wenn auch tatsächlich der Tab angezeigt werden.

Die Symbole stimmen zum Display sinngemäß überein, wobei Blinken noch nicht richtig dargestellt wird. Zwar ist die Latenz im Heimnetz gering genug, um den Displayinhalt quasi live zu übertragen, allerdings halte ich das für deutlich übertrieben. Besser und interessanter wäre hier, das Verhalten der Symbole im Backend zu analysieren, ein Blinken (und dessen Frequenz) zu erkennen und dies als Metainformation zu übertragen.

Beim Displaytext habe ich noch nicht herausgefunden, was da tatsächlich übertragen wird. Es scheint zumindest keine 1:1-Kopie der Anzeige am Regler zu sein. Für Hinweise bin ich dankbar.

Im Debug-Tab findet sich etwas Statistik und die Möglichkeit, Backend-Traces zu erstellen. Diese können durch einen erneuten Klick auf "Start" auf die ausgewählte Zeit verlängert werden. Die Aufzeichnung läuft im Hintergrund - also auch wenn die Seite geschlossen wurde - weiter. Mit einem Klick auf "Stopp" kann die Aufzeichnung vorzeitig beendet werden.

Mit der Option "Rohdaten anzeigen" können im Tab "Aktuelle Daten" die Rohfassung der beiden Datentypen im Hex-Viewer angezeigt werden.

Aktuell noch nicht implementiert aber geplant ist die Anzeige einer Heatmap z. B. über die Kollektortemperatur. Für den ersten Prototyp habe ich ältere Daten von der Solaranlage meiner Eltern verwendet. Die weißen Linien stellen Sonnenauf- und Untergang sowie den Zenit dar:

Wie und bis wann die Integration erfolgt, kann ich noch nicht sagen.


Wenn man Protokolle genauer verstehen will, hilft es häufig, den aktuellen Datenverkehr sehen und kommentieren zu können.

Für diesen Zweck gibt es die Datei trace.htm:

It's (a little) bigger on the inside. Lässt man das Kommentarfeld bei einem Druck auf Enter leer, wird ein Marker gesetzt. Über die Cursortasten können ferner die letzten Kommentare abgerufen werden. Über den Save-Button können die empfangenen Traces als HTML-Fragment exportiert werden.

Anzumerken ist, dass die vom Server kommenden Zeilen den Timestamp vom Server verwenden, Kommentare jedoch die lokale Systemzeit. Für die Synchronisation wird im zurückgesendeten Datenpaket die aktuelle Serverzeit hinzugefügt aber momentan noch nicht verwendet. Marker und Kommentare werden aber an der richtigen Stelle in die Logs eingefügt, somit sollte ein Sprung in der Zeit zwischen Daten und Kommentaren kein wirkliches Problem darstellen.

Konfiguration

Für die Konfiguration müssen im Normalfall nur zwei Dateien angepasst werden:

prozeda_systems.py

In dieser Datei werden die verschiedenen Systemkonfigurationen der Anlage abgelegt, die in der Datei pirozeda_settings.py zugewiesen und für das Aufschlüsseln der Datensätze verwendet wird.

Dabei handelt es sich um ein Dictionary über die verschiedenen Anlagentypen, deren Schlüssel (Name der Konfiguration) eine Liste der Spalten zugewiesen ist.

Hier ein Auszug der Konfiguration:

prozeda_systems = { 
    'SungoSXL_1221_de' : [
        [0x08, 'd', 'Datum'],
        [0x09, 'm', 'Zeit'],
        [0x01, 't0', 'T1 Kollektor'],
        [0x01, 't1', 'T2 Speicher 1- unten'],
        [0x01, 't2', 'T3 Speicher  oben'],
        #...
    ],
    'SungoSXLplus_1221_de' : [
        [0x08, 'd', 'Datum'],
        [0x09, 'm', 'Uhrzeit'],
        [0x10, 's', 'Sekunden'],
        # ...
    ],
    #...
}

Die Spalten enthalten folgende Elemente:

  • Spaltentyp (siehe Tabelle unten) - Int
  • Spaltenkürzel - String
  • Spaltenname - String
  • Optional(Standard: False): Spalte ausgeblendet - Boolean
  • Optional(Standard: None): Spaltenparameter - Any

Folgende Spaltentypen werden unterstützt:

Wert Länge im Datensatz Beschreibung Einheit
0x00 2 Dummy
0x01 2 Temperature °C
0x02 2 Radiation W
0x07 2 Storage kWh
0x08 2 Date
0x09 2 Time min
0x0A 1 Output
0x0B 2 Function active (Controller)
0x0C 2 Function active (status automatic/manual)
0x0D 2 Error Temperature
0x0E 2 Error Output
0x0F 2 Storage h
0x10 2 Seconds
0x13 2 Flowrate l/min
0x1B 2? Tapping l/min/10
0xFE - used for local timestamp
0xFF - Unknown entry, length given in arg

pirozeda_settings.py

Diese Datei beinhaltet die Konfiguration für den Betrieb des Backends.

Am wichtigsten aus Nutzersicht ist die Einstellung des Systems und die Angabe der Logintervalle sowie Orte für die Dateiablage. Letztere muss zwangsläufig mit absoluten Pfaden erfolgen, da der Aufruf durch Cron in aller Regel in einem anderen Arbeitsverzeichnis erfolgt.

Location entspricht der geographischen Lage der Anlage und wird momentan noch nicht verwendet. Mit ihr kann Sonnenauf- und Untergang sowie der Zenit berechnet werden, auch für die Abfrage von Wetterprognosen ist der Ort der Anlage wichtig.

settings = {
    'serialport' : {
        'port': '/dev/serial0',
        'baudrate' : 115200,
    },
    'system' : prozeda_systems['SungoSXL_1221_de'],
    'server_address' : ('', 19000),
    'fslog' : {
        # interval in seconds for the log entries to be written to the filesystem
        'interval' : 5 * 60,
        # if started via cron, an absolute path is needed!
        'dir' : '/home/pi/pirozeda/logs/',
        'prefix' : 'prozeda_',
        'suffix' : '.txt',
    },
    'ramlog' : {
        # interval in seconds for the log held in RAM
        'interval' : 1 * 60,
        # see below
        'length' : 0,
    },
    'trace' : {
        'dir' : '/home/pi/pirozeda/traces/',
        'prefix' : 'prozeda_',
        'suffix' : '.txt',
    },
    'location' : {
        'lon' : 48.3, # North
        'lat' : 10.1, # East
    }
}
#...

Installation

Die Installation ist ziemlich einfach: Das Zip-Package unter Downloads herunterladen und den Ordner "pirozeda" z.B. in das Userverzeichnis des Benutzers pi verschieben. Sollte der Pfad anders gewählt werden, muss dieser, wie oben beschrieben, angepasst werden. Hier sollte auch die Systemkonfiguration ausgewählt werden. Ein paar sind bereits in prozeda_systems.py vordefiniert.

Der Inhalt des Ordners webserver muss nach /var/www/html/.

Vor Pirozeda 0.4

Anschließend kann bzw. sollte die Ausführung des Backends als Cronjob aktiviert werden, dazu

crontab -e

ausführen und folgende Zeile hinzufügen:

@reboot python /home/pi/pirozeda/pirozeda.py

Nach einem Neustart des Pi sollten die Daten automatisch gesammelt und zur Verfügung gestellt werden.

Ab Pirozeda 0.4

Ab Pirozeda 0.4 kann das Script als Service ausgeführt werden.

Das entsprechende Script ist für die Verwendung in /home/pi/pirozeda vorbereitet und kann einfach wie folgt installiert werden:

sudo chmod +x pirozeda.py
sudo cp pirozeda.sh /etc/init.d
sudo chmod +x /etc/init.d/pirozeda.sh
sudo update-rc.d pirozeda.sh defaults
sudo /etc/init.d/pirozeda.sh start

Wer Pirozeda woanders installieren möchte, kann das Shellscript jederzeit entsprechend anpassen.

Auch die Verwendung von cron ist natürlich weiterhin möglich, allerdings muss nun python3 verwendet werden.

Update 21.04.2019: pirozeda.py muss für die Verwendung der Shell-Datei ebenfalls als ausführbar markiert sein.

Sicherheit

IoT und Sicherheit findet man oft im Kontext Probleme, Lücken und Missbrauch.

Ganz ehrlich: hier wurde nicht auf Sicherheit geachtet.

Allgemein halte ich es für sinnvoller, den Reader nur im Intranet, also ohne Erreichbarkeit von außen, einzurichten.

Erste Schritte zu mehr Sicherheit wären:

  • Das Backend nicht mehr als Root laufen lassen
  • Striktere Parameterprüfung in den PHP-Scripts oder selbige durch etwas besseres ersetzen
  • Benutzerauthentifikation für den Webserver aktivieren
  • SSL-Zertifikat installieren
  • ...

Anmerkungen

  • Vielen Dank an Hans für die Leihgabe der Prozeda-Hardware gegen die ich testen und neue Erkenntnisse gewinnen konnte
  • Für einfacher Firmware-Updates sollte zusätzlich die Rx-Leitung des AVR verbindbar und ein entsprechender Bootloader vorhanden sein. Am besten wäre wohl ein ADUM1301 (o. ä.) mit dem ein Update ohne physischen Zugriff auf die Hardware möglich wäre.
  • Dem Adapter würde eine Status-LED nicht schaden
  • Wenn pirozeda.py crasht, wird sie nicht neu gestartet. Ein Shellscript, das die Ausführung in eine Schleife nimmt, sollte ausreichen.

Leiterkarten

Es gibt noch unbestückte Leiterkarten. Wer eine will, kann sich gerne bei mir melden.

Git

Das Projekt liegt auch bei GitHub: https://github.com/chris-heo/pirozeda

Download

Vor Pirozeda 0.4

Datei:Pirozeda.zip enthält:

  • Designdaten im EAGLE-Format
  • Firmware (Atmel Studio-Projekt und vorkompiliert) für den ATmega88PA
  • Python-Backend
  • Webfrontend

Ab Pirozeda 0.4

Datei:Pirozeda rpi.zip enthält:

  • Python-Backend
  • Webfrontend

Änderungen

2017-11-??: Version 0.1

  • Erstes (nicht-öffentliches) PoC

2018-04-15: Version 0.2

  • Erster öffentlicher Release

2018-08-05: Version 0.3

  • Falsche Anzeige von Speicher [kWh] Speicher [h], Volumenstrom [l/min] korrigiert
  • Timingproblem mit Displaydata-Nachrichten mit bestimmten Reglern korrigiert
  • Ausgabe von Konsolen-Nachrichten vom pyjsonrpc-Webserver deaktiviert
  • "no data received" wird nun mit einem Zeitstempel ausgegeben
  • Zwei (etwas zusammengehackte) Scripts zum Dekodieren von (Backend-)Logs hinzugefügt
  • Firmware sendet nun 200 Header-Nachrichten nach dem Einschalten des Decoders. Aktuell findet keine Auswertung statt - dies ist im Backend noch nicht implementiert. Diese Nachrichten dienen der einfacheren Erstellung von Konfigurationen und zur Diagnose
  • 3D-Diagramm für historische Daten hinzugefügt. Achtung: Das vollständige plotly.js ist nun eingebunden. Es ist verdammt groß und daher für mobile Endgeräte eher weniger geeignet. Ferner scheint Firefox Quantum nach Zerstörung des WebGL-Kontexts den GPU-Speicher nicht wieder freizugeben. (Chrome funktioniert besser)
  • Datumswerte für historische Daten werden nun vorbelegt
  • Displayanzeige entspricht nun der am Regler (dreizeilig) und ein paar Zeichen mehr werden richtig übersetzt
  • Zeitdrift beim Logging verringert

2018-08-27: Version 0.31

  • Fehler korrigiert, weswegen aktuelle + Displaydaten sowie Statistik nicht aktualisiert werden. Das ist aufgrund des eingefügten 3D-Diagramms, was beim Laden/Aktualisieren der Daten (kontextsensitiv) nicht berücksichtigt wurde. (Notiz an mich: Tabs in Zukunft nicht per Index sondern Referenz abfragen)

2019-03-30: Version 0.4

  • Umstellung Auf Python 3
  • Script kann nun als Service verwendet werden
  • Fehlerkorrektur Anzeige von Volumenstrom
  • Mehr Informationen von der Firmware (Version/Build/Reglerversion/...)

Ich habe leider keine Vergleichsdaten aber mir scheint die CPU-Last auf dem Pi bei aktivem Frontend sehr hoch, kann das jemand bestätigen?