(WS19-01)Wetterballon Entwicklung

Aus Verteilte Systeme - Wiki
Version vom 18. März 2020, 12:33 Uhr von Amuel001 (Diskussion | Beiträge) (Versenden einer SMS über den Pi)

Wechseln zu: Navigation, Suche

Formalia

Um einen einheitlichen Stil bei der Entwicklung einzuhalten, haben wir vorarb einen C-Styleguide aufgesetzt. Die Dokumentation erfolgt über Doxygen. Für die Speicherung der Messdaten haben wir uns für das CSV-Format entschieden, da dies einfach und schnell auszuwerten ist. Nachfolgend sind die jeweiligen Artikel zu finden.

C-Styleguide

CSV-Format

Doxygen

Implementierung

Als Implementierungssprache standen Python und C zur Auswahl. Unsere Wahl fiel auf C, da damit alle Gruppenmitglieder schon erste Erfahrungen sammeln konnten. Zur Ansteuerung der über I²C angeschlossenen Sensoren standen uns die Schnittstellen i2c-dev, WiringPi und die Registermanipulation mittels der bcm2835-Bibliothek zur Auswahl. Um die passende Methode zu finden, haben wir uns mit allen Varianten beschäftigt und die Vor- und Nachteile wie folgt zusammengefasst:

i2c-dev:
+ einfache Verwendung
- adapter_nr muss ermittelt werden
- Umweg über Filesystem -> schlechtere Performance

WiringPi:
+ sehr einfache Verwendung
o bis auf Setup, zu unflexible Funktionen (Alternative: Verwendung von write und read)
- Umweg über Filesystem -> schlechtere Performance

Low Level (Registermanipulation):
+ potentiell beste Performance
+ maximale Kontrolle
+ Unabhängigkeit von I2C-Bibliotheken -> bessere Portabilität bei identischem Prozessor auf unterschiedlichen Betriebssystemen
o root-only
- komplexer -> fehleranfälliger, zeitraubender, aber es gibt eine gute Vorlage zur Implementierung

Nach der Abwägung der Vor- und Nachteile haben wir uns für eine Implementierung mittels der bcm2835-Bibliothek entschieden, da uns die bessere Performance den größten Vorteil bei der Erfüllung der Anforderungen bot, auch wenn die Benutzerfreundlichkeit darunter litt.

Versenden einer SMS über den Pi

Um die Bergung nach der Landung zu vereinfachen, haben wir die GPS-Daten regelmäßig per SMS übermittelt. Neben dem Pi und dem GPS-Modul benötigten wir einen USB-Dongle (Huawei E3372), eine Sim-Karte und folgende Softwarepakete für den Pi:

usb-modeswitch usb-modeswitch-data smstools

Da der USB-Surfstick vom Pi nur als Massenspeicher erkannt wird, muss dieser in den Modem-Modus gewechselt werden:

sudo usb_modeswitch -v 12d1 -p 1f01 -M '55534243123456780000000000000011062000000100000000000000000000'

Die passenden Werte für -v und -p können über das Terminal mittels lsusb ermittelt werden.

Leider hat diese Methode unseren Stick nicht in den passenden Modus gebracht. Es musste zusätzlich unter /ets/usb_modeswitch.d/ eine Datei mit dem Namen 12d1:1f01 und folgendem Inhalt angelegt werden:

# Huawei E3372 (fallback mode)
TargetVendor=  0x12d1
TargetProduct= 0x155f
MessageContent="55534243123456780000000000000011063000000100010000000000000000"

Es muss außerdem die Datei /etc/smsd.conf mit folgendem Inhalt vorhanden sein (die Datei wird in der Regel automatisch mit korrektem Inhalt angelegt, die Auflistung hier erfolgt nur der Vollständigkeit halber):

devices = GSM1
outgoing = /var/spool/sms/outgoing
checked = /var/spool/sms/checked
incoming = /var/spool/sms/incoming
logfile = /var/log/smstools/smsd.log
infofile = /var/run/smstools/smsd.working
pidfile = /var/run/smstools/smsd.pid
outgoing = /var/spool/sms/outgoing
checked = /var/spool/sms/checked
failed = /var/spool/sms/failed
incoming = /var/spool/sms/incoming
sent = /var/spool/sms/sent
stats = /var/log/smstools/smsd_stats
loglevel = 7

#
# Hier stehen u.U. eine Menge auskommentierter Einträge, die relevanten Zeilen befinden sich ganz am Ende der Datei
#

[GSM1]
device = /dev/ttyUSB0 # Das korrekte device kann mit ls -la /dev/serial/by-id ermittelt werden
incoming = yes 
baudrate = 9600

Nun kann der Pi eine SMS verschicken. Dazu muss einfach nur eine Datei in den Ordner /var/spool/sms/outgoing/ verschoben werden. Zu beachten ist dabei, dass die Datei nach folgendem Schema aufgebaut ist.

To: Empfängernummer(Bsp: 4915201234567)

Text, der übermittelt werden soll.

Eine kleine Funktion zum Testen:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>

void send(char *message) {

	int fd_to, geschrieben;
	char *path = "/var/spool/sms/outgoing/data.txt";

	fd_to = open(path, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);

	if (fd_to == -1) {

		printf("Error opening file");
		return;
	}

	printf("Größe: %d\n", strlen(message));
	printf("Geöffnet\n");
	char *msg = "To: xxxxxxxxxx\n\n";
	
	char *fullmsg = malloc(strlen(msg) + strlen(message) + 1);
	strcpy(fullmsg, msg);
	strcat(fullmsg, message);
	strcat(fullmsg, "\n\0");
	 	
	geschrieben = write(fd_to, fullmsg, strlen(fullmsg));
	free(fullmsg);
}

int main(int argc, char *argv[]) {

	send("Hallo Welt");
	return 0;
}

Auswertungsprogramm

Auswertung der Messdaten

Um die aufgezeichneten Daten nach dem Flug grafisch aufzuarbeiten, wurde ein eigenes Tool geschrieben. Hier gab es keine großen Einschränkungen, was die Programmiersprache betrifft, aufgrund von Erfahrung und persönlicher Präferenz wurde sich für Java entschieden. Das Tool ist speziell auf die von uns verwendeten Sensoren und das Vormat der CSV-Dateien zugeschnitten, alledings Modular aufgebaut und somit leicht erweiterbar.

Über eine grafische Oberfläche kann ausgewählt werden, welche Werte auf x- und y-Achse abgebildet werden sollen. Danach muss für jeden ausgewählten Datentyp eine Datei mit den entsprechenden Daten angegeben werden. Hierbei merkt sich das Programm die letzten gewählten Dateien um bei häufiger Benutzung Zeit zu sparen. Des Weiteren können Grenzwerte für beide Achsen fesgelegt werden und es steht die Wahl offen, ob alle ausgewählten Werte in einem oder in mehreren Diagrammen dargestellt werden sollen.

Die Erzeugung der Diagramme folgt dem Factory-Method Designpattern. In einer abstrakten Klasse wird die Standardprozedur zur Erstellung eines Diagramms definiert. Für jeden Typ von Daten, den wir aufzeichnen (Temperatur, Luftdruck, etc.) gibt es dann eine konkrete Implementierung dieser Klasse, die entweder die Standardimplementierung übernehmen kann (was i.d.R. der Fall ist) oder diese überschreibt.

Auswertung der GPS-Daten

Zuerst wurden die ebenfalls im CSV-Format gespeicherten GPS-Daten als Route in einer GPX-Datei umgewandelt. Dazu werden die Aufzeichnungsdaten zeilenweise von einem Javaprogramm eingelsen und in eine neue Datei im GPX-Format, das vom Aufbau einer XML-Datei gleicht, geschrieben. Diese Route sollte ursprünglich mittels eines XSLT-Skripts und einem OSM-Kartenausschnitt zu einer SVG-Datei transformiert werden, allerdings scheiterte dieses Vorhaben daran, dass der Kartenauschnitt für unsere Flugroute über 50 GB an Dateigröße überschritten hat und somit nicht mehr zu bearbeiten war. Die Darstellung der in der GPX-Datei erfassten Route wurde dann von Online-Tools wie Google-Maps übernommen.

Zusätzlich wurde zu Testzwecken eine Funktion eingebaut, die eine CSV-Datei mit GPS-Koordinaten erzeugt.

Bedienung
In das obere Textfeld wird der Pfad zum Ordner eingetragen, in der sich die auszuwertende CSV-Datei befindet
In das untere Textfeld wird der Dateiname inkl. Dateiendung eingetragen
Sollte der Haken in die Checkbox gesetzt werden, wird eine CSV-Datei erzeugt mit dem in dem Textfeld darüber angegebenen Namen in dem im Textfeld angegebenen Ordner erzeugt.

Wichtig: Sollte bereits eine Datei mit gleichem Namen vorhanden sein, wird diese überschrieben.

Code
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.net.URI;
import java.net.URISyntaxException;
import javax.swing.*;
import java.awt.event.ActionListener;

public class Auswertung extends JFrame implements ActionListener {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	static String path, inFile;
	static String outFile = "route.gpx";
	boolean genericFile = false;
	static String htmlFile = "file:///C:/Users/Manue/Desktop/Auswertung/Auswertung.html";
	JTextField tfFolder = new JTextField(50);
	JTextField tfName = new JTextField(50);

	static double lat = 50.026411;
	static double lon = 8.007177;
	static double h = 310.00;
	static double time = 6513.264;
	static Random rng = new Random();
	static int rangeMin = 0;
	static int rangeMax = 1000;
	static int anzahlDaten = 12000;
	static boolean abstieg = false;

	JLabel label;
	JPanel panel;
	JCheckBox gen;
	JButton buttonOK;

	public static double rng() {
		return rng.nextDouble() / 3600;
	}

	// Erzeugt Flugaufzeichnungen als txt
	public static void generateGpsData(String in) throws IOException {
		BufferedWriter writer = new BufferedWriter(new FileWriter(in));
		System.out.println("Generator: Datei erzeugt");
		writer.write("\"Unix Time (s)\";\"Latitude (DD)\";\"Longitude (DD)\";\"Height (m)\"\r\n");
		System.out.println("Generator: Header geschrieben");
		for (int i = 0; i < anzahlDaten; i++) {
			writer.write(time + ";" + lat + ";" + lon + ";" + h + "\r\n");
			lat -= rng();
			lon += rng() / 3;
			time += 1.0;
			if (h < 30000 && abstieg == false) {
				h += 7.0;
			} else {
				abstieg = true;
				if (h > 5) {
					h -= 5.0;
				} else {
					h = 0.0;
				}
			}
		}
		System.out.println("Generator: Datensäte erzeugt");
		writer.close();
		System.out.println("Generator: Ende");
	}

	// GUI
	public Auswertung() {
		JFrame meinJFrame = new JFrame();
		meinJFrame.setTitle("GPS-Daten auswerten");
		meinJFrame.setSize(700, 150);
		panel = new JPanel();

		label = new JLabel("Dateiordner angeben");
		panel.add(label);

		panel.add(tfFolder);

		label = new JLabel("Dateinname angeben");
		panel.add(label);
		panel.add(tfName);

		gen = new JCheckBox("Generische Daten erzeugen", genericFile);
		gen.addActionListener(this);
		panel.add(gen);

		buttonOK = new JButton("OK");
		buttonOK.addActionListener(this);
		panel.add(buttonOK);

		meinJFrame.add(panel);
		meinJFrame.setVisible(true);

	}

	// Erzeugt aus der txt eine .gpx Datei
	public static void convertToGpx(String in, String out) throws IOException {
		FileReader reader = new FileReader(in);
		System.out.println("Converter: Datendatei gefunden");
		
		BufferedReader inBuffer = new BufferedReader(reader);
		System.out.println("Converter: BufferedReader initialisiert");
		
		BufferedWriter writer = new BufferedWriter(new FileWriter(out));
		System.out.println("Converter: Writer erzeugt ");
		
		String line = inBuffer.readLine();
		System.out.println("Converter: Erste Line gelesen");
		
		line = inBuffer.readLine();
		System.out.println("Converter: Header übersprungen");
		
		writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\r\n"
				+ "<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" version=\"1.1\" creator=\"Wikipedia\"\r\n"
				+ "    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
				+ "    xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\r\n"
				+ " <!-- Kommentare sehen so aus -->\r\n" + " <metadata>\r\n" + "  <name>" + outFile + "</name>\r\n"
				+ "  <desc>GPS-Aufzeichnung vom 11.03.2020</desc>\r\n" + "  <author>\r\n" + "   <name>MF</name>\r\n"
				+ "  </author>\r\n" + " </metadata>\r\n" + " <trk>\r\n" + "  <name>Trackname1</name>\r\n"
				+ "  <desc>Trackbeschreibung</desc>\r\n" + "  <trkseg>\r\n");
		System.out.println("Converter: GPX-File Header geschrieben");
		
		while (line != null) {
			String[] splitString = line.split(";");
			for (int i = 0; i < splitString.length; i++) {
				System.out.println(splitString[i]);
				
			}

			writer.write("   <trkpt lat=\"" + splitString[1] + "\" lon=\"" + splitString[2] + "\">\r\n" + "    <time>"
					+ splitString[0] + "</time>\r\n" + "    <ele>" + splitString[3] + "</ele>\r\n" + "   </trkpt>\r\n");

			line = inBuffer.readLine();
		}
		System.out.println("Converter: GPX-File Trackpts geschrieben");
		
		writer.write("  </trkseg>\r\n" + " </trk>\r\n" + "</gpx>");
		System.out.println("Converter: GPX-File geöffnete Tags geschlossen");
		
		writer.close();
		inBuffer.close();
		reader.close();
		System.out.println("Converter: Ende ");
	}

	// Soll die Lokale Datei unter dem Pfad "htmlFile" in MS Edge öffnen, da nur
	// dieser Browser
	// die Auswertungsseite korrekt darstellt
	public static void open() throws IOException {
		System.out.println("Versuche, Browser zu öffnen");
		try {
			System.out.println("cmd.exe /C start microsoft-edge:" + htmlFile);
			Runtime.getRuntime().exec("cmd.exe /C start microsoft-edge:" + htmlFile);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println("Error beim Öffnen von Edge");
		}
		System.out.println("Browser geöffnet");
	}

	// Alternative Methode, die einen GPX Viewer im Standardbrowser und den
	// Speicherort der gpx öffnet.
	// Die gpx Datei muss nur ins Browserfenster gezogen werden
	public static void open2() throws IOException {
		System.out.println("Versuche, Browser zu öfnnen");
		try {
			Desktop.getDesktop().browse(new URI("https://www.j-berkemeier.de/ShowGPX.html"));
		} catch (IOException | URISyntaxException e) {
			// TODO Auto-generated catch block
			System.out.println("Error beim Öffnen des Browsers");
		}
		System.out.println("Browser geöffnet");
		Desktop.getDesktop().open(new File(path));
	}

	public static void main(String[] args) {
		new Auswertung();
	}

	// Actionhandler, der prüft, ob die Checkbox zur Datenerzeugung angekreuzt
	// wurde. Falls ja, werden Flugdaten
	// erzeugt beim Klick auf OK erzeugt.
	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == this.gen) {
			genericFile = !genericFile;
		} else {
			path = tfFolder.getText();
			inFile = tfName.getText();
			System.out.println(path);
			if (genericFile) {
				try {
					generateGpsData(path + inFile);
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					System.out.println("Error on generate()");
				}
			}
			try {
				convertToGpx(path + inFile, path + outFile);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println("Error on convert()");
			}
			try {
				open();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println("Error open()");
			}
		}
		System.out.println(genericFile);
	}
}