(WS19-01)Wetterballon Design

Aus Verteilte Systeme - Wiki
Wechseln zu: Navigation, Suche

Hardware-Architektur

HW Architecture.png

Als zentrale Steuereinheit haben wir einen Pi-Zero benutzt, um Gewicht und Strom zu sparen. Ein selbst gebauter HAT, der uns zur Verfügung gestellt wurde, stellte u. a. die notwendigen I²C-Anschlüsse bereit, um die Sensoren mit dem Pi zu verbinden. Auf dem HAT befanden sich außerdem Module für die Lokalisierung per GPS und das Versenden von Daten über LoRaWAN, sowie GPIO-Anschlüsse die wir für einen Buzzer verwendet haben, der die Bergung unterstützen sollte. Über ein USB-Hub wurden eine zusätzliche Kamera und ein Modul für die Datenübertragung per GSM angeschlossen. Der Zusammenschluss von Pi, HAT, USB-Hub und Speicherkarte wurde "master" getauft. Es befand sich außerdem ein GPS-Tracker im Payload, der unabhängig vom eigentlichen System war und bei einem Stromausfall die Bergung ermöglichen sollte.

Verbaute Sensoren und Geräte

Um den Prioritäten in den Anforderungen gerecht zu werden, wurden wichtige Sensoren mehrfach und mit unterschiedlichen Modellen verbaut. Neben dem internen, das per LTE-USB-Stick regelmäßig Daten versendet, liegt noch ein externes GPS-Gerät vor.

Funktion Anzahl Name
Temperatur innen 1 MCP9808
Temperatur außen 1 TMP 117 Breakout
Luftfeuchtigkeit außen 1 SHT 35
Luftfeuchtigkeit außen 1 SHT 31
Luftfeuchtigkeit innen 1 SHT 35
Luftdruck außen 1 MS8607
Luftdruck außen 1 MS5611 auf GY-86
Luftdruck innen 1 MS8607
9-Achsen (3-Achsen-Beschl., -Gyro., -Magnet.) 1 IMU 10DOF mit MPU-9250 & BME280
UV-A und UV-B 1 GYML8511 an Grove I2C-ADC
Kamera Bild 1 WaveShare IMX179 USB-Kamera
Kamera Video 1 Raspicam V2
GPS 1 u-blox NEO-M8U
Buzzer 1 Grove Buzzer
Real-Time-Clock 1 PiFace Shim RTC
PI-Erweiterung (Grove-I2C, LoRa, GPS, ...) 1 weather-balloon-hat

Aufbau des Payloads

Auf Basis der Hardware-Achritektur wurde ein Design des Payloadaufbaus in FreeCAD erzeugt.


Payload1.jpg Payload3.jpg Payload5.jpg Payload6.jpg


Aus dem Entwurf wurde noch eine 3D-PDF-Datei erzeugt, die sich nur mit dem Acrobat Reader öffnen und betrachten lässt.

Datei:Payload3D.pdf

In diesem Design fehlen noch die Sensoren sowie die Status-LEDs, da deren Position aufgrund der flexiblen Befestigungsmethoden zu dem Zeitpunkt noch unbekannt war.

Die Sensoren für den Innenraum wurden nur mit Klebeband an der Innenseite befestigt. Die äußeren Sensoren wurden mit Draht in der Styroporbox verankert. Für die LEDs wurde jeweils ein passendes Loch in die Box gebohrt durch das sie nach außen geführt und nicht separat befestigt wurden.

Der Designvorschlag wies im Laufe des Projekts einige Schwächen auf.

  1. Durch die passgenaue Form der Bodenplatte konnte das Gestell nicht mehr ohne das Entfernen der inneren Sensoren aus der Styroporbox entfernt werden. Dies war notwendig, um bspw. ein Wärmepad zu platzieren oder die USB-Kamera nachzujustieren.
  2. Die Halterung des Pis wurde zu nah an der Innenwand platziert, sodass das Flachbandkabel der Kamera sehr stark geknickt und oft aus dem Port gezogen wurde.


Anbindung

I2C

Fast alle Sensoren benutzen zur Kommunikation I2C, ein Bus zur synchronen Datenübertragung, der auf einem Master-Slave-Prinzip basiert. Die Slaves sind über Adressen ansprechbar und antworten auf Anfragen vom Master.

In unserem Fall stellt das master-Modul auch den Master des Busses dar, an dem die Sensoren als Slaves angeschlossen sind. Dieser sendet Signale aus, um Messungen anzustoßen oder Daten auszulesen. Diese Signale werden sensorabhängig als akute Befehle oder als Lese-/Schreibbefehle für Register auf dem Sensor interpretiert.

Mehr dazu: Wikipedia.

Seriell

Der GPS-Empfänger, der auf dem Hat verbaut ist, benutzt die serielle Schnittstelle zur Kommunikation. Das master-Modul kann also auf die Daten zugreifen, indem es aus der entsprechenden Datei im /dev/-Verzeichnis ließt. Sobald die Daten einmal ausgelesen werden, sind sie jedoch nicht mehr verfügbar.

Digital

Da der Buzzer nur an- oder ausgeschaltet werden kann, wird er direkt über die GPIOs gesteuert, indem deren Modus auf Output gesetzt wird und die Spannung abwechselnd angelegt und abgenommen wird.

Probleme

Arbeitsbereiche

Bei den vorhandenen Sensoren sind wir auf kleinere Probleme gestoßen. Alle Sensoren sind bis zu einer Minimaltemperatur von -40°C funktionsfähig. Auf dem Flug werden diese allerdings deutlich unterschritten. Wir haben uns dennoch für den Einsatz dieser Sensoren entschieden, da auf dem Markt keine geeigneteren Sensoren zu finden waren. Um Messungenauigkeiten oder gar Ausfälle zu kompensieren, werden wir die Sensoren für den Außeneinsatz mehrfach verbauen.

Adresskonflikte

Da manche Sensoren dieselben Adressen besitzen, haben wir einen Multiplexor verwendet, der vor Ansteuerung der Sensoren mit identischen Adressen auf den korrekten Ausgang geschaltet werden muss. Dazu hat unser Betreuer Kai Beckmann eine zum Grove-System passende Platine entworfen und fertigen lassen.

Multiplexor PCB.pdf

Multiplexor Schematic.pdf

System-Zeit ohne Internet (RTC)

Üblicherweise wird die Systemzeit des PiZeros bei Neustart anhand einer Internetverbindung gesetzt und synchronisiert. Um das System davon unabhängig zu machen, ist eine Real-Time-Clock mit eigener Batterie verbaut.

Software-Architektur

Grundsätzliches

Bei der Software-Architektur stellte sich die Frage, wie die unterschiedlichen Messzyklen der Sensoren am besten zu verwalten sind. Hier gab es zwei grundsätzliche Ideen.

Parallel

Eine parallele Implementierung mit Threads bietet den Vorteil, dass jeder Sensor autonom läuft, also auch in keiner Weise von den anderen Sensoren abhängig ist. Jeder Sensor muss nur eine (oder für jede Phase eine) run-Methode implementieren, die vom Master gestartet wird. Der Zugriff auf den Bus kann per Mutex synchronisiert werden. Beim Wechsel des Modus kann der Master dann jeden Thread beenden.

Die Nachteile dieser Methode sind größtenteils erst nach einer Probeimplementierung aufgefallen. Dazu zählen neben der ohnehin höheren Komplexität paralleler Programme, dass die Abgrenzung der Sensoren untereinander auch dazu führt, dass sie weitestgehend vom Master entkoppelt sind. Das ist vor allem im Bezug auf die Live-Datenauswertung ein Problem, da ein einzelner Sensor keinen simplen Weg hat, mit dem Master zu kommunizieren. Des Weiteren führt die Anforderung der Unterbrechbarkeit in Verbindung mit der Synchronisierung per Mutex zum Problem von Race-Conditions, die, wenn nicht korrekt behandelt, das komplette Programm lahmlegen könnten.

Aus diesen Gründen wurde sich gegen die Implementierung mit Threads entschieden.

Monolithisch

Eine monolithische Implementierung mit einer einzigen Hauptfunktion, die alle Sensoren und andere Funktionalitäten, wie das Schreiben der Daten in Dateien, überwacht, sah zunächst nach einem uneleganteren Weg aus, da ihr Hauptvorteil im Vergleich zu Threads lediglich die einfachere Programmierung zu sein schien.

Neben der Lösung für die Problematik der Live-Datenverarbeitung bietet dieser zentralisierte Ansatz allerdings noch einige andere Vorteile. Die Komplexität des Codes wird verringert und die Implementierung der Wartungsschnittstelle wird vereinfacht. Außerdem eröffnet dieser Ansatz die Möglichkeit für weitere Dateninteraktionen. Des Weiteren müssen gleiche Abläufe nicht mehr für jeden Sensor redundant programmiert, sondern nur einmal im Master geschrieben werden.

Durch die engere Bindung der einzelnen Komponenten wurde eine größere, gut durchdachte und klar definierte Schnittstelle für die Sensoren nötig, die hilft, den Master algorithmisch übersichtlich zu halten, die aber auch auf den Eigenheiten der unterschiedlichen Sensoren gerecht wird.

Überblick

Komponentendiagramm.jpg

Messdatenstruktur

Die Messdaten der Sensoren müssen über Programmteile hinweg kommuniziert werden können, z. B. zur internen Verarbeitung oder zur Serialisierung. Um eine solch vielseitige Nutzung zu ermöglichen, braucht es eine Datenstruktur, die nicht nur Zahlenwerte speichert sondern ihnen auch einen Kontext gibt. Daher ist für jeden Messwert der zugehörige Messdatentyp gespeichert.

Sensorschnittstelle

Alle Methoden dieser Schnittstelle sind mit Rückgabewerten definiert, die eindeutig Fehlercodes darstellen können. Das erlaubt von außen zu entscheiden, ob eine Fehlermeldung relevant ist und z. B. zum Programmabbruch führen soll, oder ob anderweitig bzw. überhaupt auf sie reagiert werden muss.

sensor_init(int)
Vor allem komplexere I²C-Sensoren bieten die Möglichkeit, sie über ihre Register zu konfigurieren. Für solche Sensoren existiert diese Funktion. Ist die Initialisierung mehrstufig, kann die Anzahl verbleibender Schritte angegeben werden. Dies ist vor allem bei einer erneuten Initialisierung des Sensors hilfreich.
sensor_measure(measurement_data*, int)
Diese Funktion liefert die Messdaten eines Sensors in der übergebenen Messdatenstruktur zurück. Auch das Messen kann mehrstufig erfolgen, um z.B. Analog-Digital-Wandlern Zeit zur Umwandlung zu verschaffen.
sensor_is_connected()
Diese Funktion bietet die Möglichkeit, einen Sensor zu prüfen, ob dieser angeschlossen und ansprechbar ist. Sollte diese Funktion einen zwischenzeitlichen Ausfall anzeigen, sollte der Sensor neu initialisiert werden, bevor weitere Messungen durchgeführt werden.

Master

Erster Entwurf

Der Aufbau des Master-Programms war zunächst so gestaltet, dass in einem ersten Schritt alle Bibliotheken geladen, Persistenzdaten gelesen, Sensoren und deren Messdatenstrukturen initialisiert und alle weiteren einmaligen Vorbereitungen durchgeführt werden. Nach den Vorbereitungen wurde die Hauptschleife betreten, die das periodische Aufrufen der Sensoren übernahm. Dazu wurde eine feste Laufzeit für einen Schleifendurchlauf festgelegt, welche mit passivem Warten nach der Sensorabarbeitung umgesetzt wurde. Das Timing jedes Sensors konnte somit in feste Schleifenintervalle umgerechnet werden. Falls die Schleife ihre für Testzwecke eingebaute Abbruchbedingung erreich hat, folgte der Ressourcenabbau in umgekehrter Reihenfolge der Initialisierung.

Vereinfachte Hauptschleife des ersten Entwurfs:

// Main loop.
for (unsigned long iteration = 0; QUIT != active_phase; ++iteration) {
	// Take starting time.
	clock_gettime(CLOCK_MONOTONIC, &iteration_start);

	// Check every sensor.
	for (size_t i = 0; SENSOR_AMT > i; ++i) {
		if (0 <= errors_g[i] && SENSORS[i].active_phase & active_phase) {
			// Block all signals.
			sigprocmask(SIG_BLOCK, &all, &normal);

			// Take measurements from all sensors active on this iteration.
			if (0 == iteration % frequency_to_iteration(SENSORS[i].frequency * multipliers_g[i])) {
				SENSORS[i].measure(&data[i]);
			}

			// Get data from all sensors which have available data on this iteration.
			if (frequency_to_iteration(SENSORS[i].measure_delay * multipliers_g[i]) == iteration % frequency_to_iteration(SENSORS[i].frequency * multipliers_g[i])) {
				SENSORS[i].get_data(&data[i]);
			}

			// Reset blocked signals.
			sigprocmask(SIG_SETMASK, &normal, NULL);
		}
	}
}

Nach der anfänglichen Entwicklung mit dieser Programmstruktur fiel auf, dass das Konzept für eine zufriedenstellende Implementierung der benötigten Funktionalitäten unzureichen war und ein flexibleres Scheduling-Verfahren akkurater und fehlertoleranter wäre. Zusätzlich sind weiter Aufgaben, wie das Ansprechen der Status-LEDs, angefallen, die sich nicht sauber über die Sensorschnittstelle hätten implementieren lassen können und fest in das master-Programm hätten eingearbeitet werden müssen. Es war also ein weiterentwickeltes Design nötig.

Zweiter Entwurf

Master-ablaufdiagramm-Uebersicht.png Master-ablaufdiagramm-Initialisierung.png Master-ablaufdiagramm-Task-Loop.png

Das Kernkonzept der Neuerung war das o.g. Scheduling-Verfahren und die Diversifizierung der auszuführenden Aufgaben. Das Problem der unterschiedlichen Aufgaben ließ sich relativ einfach lösen, indem nun nicht mehr nur Sensoren, sondern allgemeine Task verschiedener Typen verwaltet werden können. Sensoren sind damit zu Sensor-Tasks geworden.

Als Scheduling-Verfahren wurde eine eine Art Queue gewählt, in der jedoch zu jedem Zeitpunkt alle Tasks stehen. Die Auswahl der als nächstes auszuführenden Aufgabe erfolgt per Zeitstempel, der im Task gespeichert ist und vom Task selbst verwaltet wird. Dieser Zeitstempel zeigt den nächsten gewünschten Ausführungstermin an. Es wir immer der Task mit dem am nächsten in der Zukunft liegenden Zeitstempel ausgewählt. Sollte ein Task in Verzug geraten, wird sein Zeitstempel vom Scheduler in die Gegenwart korrigiert.

Die Rahmenstruktur mit Laden und Initialisieren der Komponenten zu Beginn und dem Aufräumen der Ressourcen am Ende blieb weitestgehend bestehen und wurde lediglich durch Auslagerung in eigene Teil-Funktionen modularisiert. Ergänzt wurde die Initialisierung der Sensor- und anderweitiger Tasks.

Vereinfachte Initialisierung eines Tasks:

// Master LED
tasks[s_con_amt].type = LED_MASTER;
tasks[s_con_amt].state = RUNNING;
tasks[s_con_amt].active_phases = START;
clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt].next_time));

Vereinfachte Hauptschleife des zweiten Entwurfs:

// Main loop
while (QUIT != active_phase && EXIT_SUCCESS == task_get_next(tasks, tasks_amt, &task)) {
	// Is task ready? If not, sleep remaining time
	clock_gettime(CLOCK_MONOTONIC, &cur_time);
	if (timespec_gt(task->next_time, cur_time)) {
		diff_time = timespec_sub(task->next_time, cur_time);
		clock_nanosleep(CLOCK_MONOTONIC, 0, &diff_time, &diff_time);
	}

	switch (task->type) {
		case SENSOR:
			sensor = (i2c_sensor *) (task->p);
			sigprocmask(SIG_BLOCK, &all, &normal);

			switch (sensor->state) {
				case INIT:
					// ...
				// ...
			}

			sigprocmask(SIG_SETMASK, &normal, NULL);
			break;
		case LED_MASTER:
			// ...
		// ...
	}
}

Eine aktuelle Übersicht zur Initialisierung ist im Projekt-Ordner unter "docs/master-ablaufplan-init.ods" zu finden.

Phasenwechsel-Algorithmus

Problem: Unterschiedliches Komponentenverhalten in verschiedenen Flugphasen erfordern einen Mechanismus zur Phasenerkennung

Lösung: Ermittlung der vertikalen Geschwindigkeit anhand der Höhenwerte aus dem Barometer

Schwierigkeit: Die Messungen unterliegen natürlichen Schwankungen und gelegentlich extremen Falschwerten.

Algorithmus:

  1. Summiere 10 Höhenwertdifferenzen unter Filterung von Falschwerten.
  2. Bilde den Mittelwert und errechne unter Kenntnis der Messfrequenz die vertikale Geschwindigkeit
  3. Wurde der Schwellwert von 0,5 m/s überschritten, registriere eine Bewegung, ansonsten einen Stillstand
  4. Unter Verwendung eines Schieberegisters wird die Phase erst bei Registrierung von 16 aufeinanderfolgenden Bewegungen auf FLIGHT gesetzt, im weiteren bei 16x Stillstand auf RECOVERY.

Fehlertoleranz

Sensoren-Ausfall

Sollten Sensoren während der Flugphase aufgrund von Umweltbedingungen ausfallen, probiert der Master in regelmäßigen Abständen den ausgefallenen Sensor anzusprechen. Sollte das gelingen, wir dieser neu initialisiert und läuft im Anschluss ganz normal weiter.

Sensor-States-Sensor-Error-Handling.png

System-Freeze (Watchdog)

Da während des Flugs von außerhalb keinerlei Eingriffmöglichkeiten ins System bestehen, haben wir den auf dem HAT integrierten Watchdog implementiert, der das System hart resetet, sollte es sich aufhängen. Dem Watchdog muss dazu über ein regelmäßiges Ein- und Ausschalten von PINs ein funktionierendes System kommuniziert werden. Bleibt dieses Signal aus, kommt es zum Neustart. Daraus ergab sich dann auch die Notwendigkeit, dass diverse Laufzeitdaten, wie etwa aktive Phase oder Kalibrierungsdaten, gespeichert und geladen werden können müssen, um ein reibungsloses Fortsetzen der Messungen nach Neustart zu gewährleisten.

Programm-Freeze (Watchpuppy)

Um die verlorene Zeit und damit verlorenen Daten durch einen Neustart, ausgelöst durch den Watchdog, zu reduzieren, wurde eine skriptbasierte Zwischenstufe umgesetzt, der "Watchpuppy". Dieser startet dann lediglich den Master-Systemd-Service neu. Das dauert dann nur noch wenige Sekunden im Vergleich zu knapp einer Minute bei einem Reboot. Dafür ist der Watchpuppy als Shell-Skript-Systemd-Service umgesetzt und prüft alle x Sekunden, ob er vom Master innerhalb des Intervalls wenigstens einmal ein Signal erhalten hat.

Damit der Watchpuppy wirksam mit dem Watchdog zusammenarbeiten kann, muss die Signal-Taktung höher als die des Watchdogs sein.