(WS19-01)Wetterballon Entwicklung: Unterschied zwischen den Versionen

Aus Verteilte Systeme - Wiki
Wechseln zu: Navigation, Suche
(Auszüge aus dem Quellcode)
(Quellcode Master & Tasks)
Zeile 32: Zeile 32:
  
 
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.
 
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.
 
== Quellcode Master & Tasks ==
 
Nach dem in der Designphase (Link) festgeleten Mustern wurde der Master, die Sensoren sowie die Tasks wie folgt implementiert:
 
<div class="mw-collapsible mw-collapsed" style="width:100%">
 
;master.h
 
<div class="mw-collapsible-content">
 
<syntaxhighlight lang = "c">
 
/**
 
* @file  master.h
 
* @brief Definitions for the Master program.
 
*
 
* @author Jonas Gaida, Florian Schwarz
 
*/
 
 
#ifndef INCLUDE_MASTER_H_
 
#define INCLUDE_MASTER_H_
 
 
#include "sensor.h"
 
 
#include "sensor_gyml8511.h"
 
#include "sensor_mcp9808.h"
 
#include "sensor_mpu9250.h"
 
#include "sensor_ms5611_out.h"
 
#include "sensor_ms8607_out.h"
 
#include "sensor_ms8607_in.h"
 
#include "sensor_sht31d_out.h"
 
#include "sensor_sht35_out.h"
 
#include "sensor_sht35_in.h"
 
#include "sensor_tmp117.h"
 
#include "sensor_camm8.h"
 
 
#include "sensor_height_sim.h" // Simulates flight height data
 
 
#include "sensor_buzzer.h"
 
 
#include "camera_raspicam.h"
 
#include "camera_usb.h"
 
 
 
/**
 
* @brief Array of all registered sensors.
 
*
 
* This needs to be manually filled.
 
*/
 
const i2c_sensor_reg SENSORS[] = {
 
MS8607_OUT,
 
MS5611_OUT,
 
MS8607_IN,
 
SHT35_OUT,
 
SHT31D_OUT,
 
SHT35_IN,
 
TMP117,
 
MCP9808,
 
MPU9250,
 
GYML8511,
 
CAMM8,
 
HEIGHT_SIM,
 
BUZZER
 
};
 
 
 
/**
 
* @brief Constant for easy access to the total number of registered sensors.
 
*/
 
const size_t SENSOR_AMT = sizeof SENSORS / sizeof (i2c_sensor_reg);
 
 
 
/**
 
* @brief Array of all cameras.
 
*
 
* This needs to be manually filled.
 
*/
 
const camera CAMERAS[] = {
 
RASPICAM,
 
USBCAM
 
};
 
 
 
/**
 
* @brief Constant for easy access to the total number of cameras.
 
*/
 
const size_t CAMERA_AMT = sizeof CAMERAS / sizeof (camera);
 
 
 
/**
 
* @brief Path of the persistence file for the phase.
 
*/
 
const char *const PERSISTENCE_PATH = "./phase";
 
 
 
 
/**
 
* @brief The phase in which the master currently is.
 
*/
 
extern phase active_phase;
 
 
 
 
int main(void);
 
 
 
// Signal handler functions
 
 
/**
 
* @brief Reads the phase saved in the file at PERSISTENCE_PATH.
 
*
 
* This method only reads if the file exists. Otherwise it does not create it
 
* with a default phase. Instead it just returns unsuccessfully.
 
*
 
* @param[out] p Pointer to a phase which will be filled.
 
*
 
* @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 
*
 
* @sa PERSISTENCE_PATH
 
*/
 
int read_phase(phase *const p);
 
 
 
/**
 
* @brief Writes the given phase to the file at PERSISTENCE_PATH.
 
*
 
* @param[in] p The phase to be written.
 
*
 
* @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 
*
 
* @sa PERSISTENCE_PATH
 
*/
 
int write_phase(phase p);
 
 
 
/**
 
* @brief Checks the health running master.
 
*
 
* Implements the function type required by a signal handler.
 
*
 
* @param[in] sig The signal which triggered the handler call.
 
*/
 
void sh_maintenance(int sig);
 
 
 
/**
 
* @brief Forces the active phase to be re-read.
 
*
 
* Implements the function type required by a signal handler.
 
*
 
* @param[in] sig The signal which triggered the handler call.
 
*
 
* @sa read_phase, active_phase
 
*/
 
void sh_update_phase(int sig);
 
 
 
/**
 
* @brief Contains logic for the FALLBACK_STATE_TO_RECOVERY_CHANGER task
 
*
 
* Ensures that master switches to RECOVERY phase after MAX_FLIGHT_TIME.
 
* To withstand forced reboot, this task writes its time for phase change to a file specified by MAX_FLIGHT_TIME_PATH
 
*
 
* @param[in,out] thiz pointer to own task struct
 
* @param[in,out] cur_phase pointer to current active phase
 
* @param[in] cur_time current time
 
*
 
*/
 
int fallback_recovery_task(task *thiz, phase *cur_phase, struct timespec cur_time);
 
#endif  // INCLUDE_MASTER_H_
 
 
</syntaxhighlight>
 
</div>
 
</div>
 
 
<div class="mw-collapsible mw-collapsed" style="width:100%">
 
;master.c
 
<div class="mw-collapsible-content">
 
<syntaxhighlight lang = "c">
 
/**
 
* @file  master.c
 
* @brief Implementation of master.h
 
*
 
* @author Jonas Gaida, Florian Schwarz
 
*/
 
 
#include "master.h"
 
 
#include <stdlib.h>  // free, exit codes
 
#include <unistd.h>  // calloc
 
#include <stdio.h>  // fprintf, fflush, stdout, stderr
 
#include <fcntl.h>  // open
 
#include <sys/stat.h>  // open flags
 
#include <signal.h>  // struct sigaction, sigaction
 
#include <string.h>  // strerror, strlen
 
#include <time.h>  // clock_gettime
 
#include <limits.h>  // INT_MAX
 
#include <errno.h>  // errno
 
 
#include "bcm2835.h"
 
#include "timespec_helper.h"
 
#include "task.h"
 
#include "task_led.h"
 
#include "task_gsm.h"
 
#include "task_watchdog.h"
 
 
 
 
/**
 
* @brief Signal which triggers the sh_maintenance function.
 
*
 
* @sa sh_maintenance
 
*/
 
static const int MAINTENANCE_SIGNAL = SIGUSR1;
 
 
/**
 
* @brief Signal which triggers the interaction function.
 
*
 
* @sa interaction
 
*/
 
static const int UPDATE_SIGNAL = SIGUSR2;
 
 
 
/**
 
* @brief Maximum time in s until state change to RECOVERY
 
*
 
* Fallback mechanism, to guarantee final RECOVERY mode.
 
*/
 
static const unsigned long MAX_FLIGHT_TIME_S = 3 * 60 * 60; // 3 hours
 
/**
 
* @brief Stores time for phase change to RECOVERY
 
*/
 
const char *const MAX_FLIGHT_TIME_PATH = "./max_flight_time.pers";
 
 
/**
 
* @brief Length of the ID suffix of each sensor's file name.
 
*
 
* This is used in a @c printf call as a '*' length field. These expect @c int
 
* values. Therefor the value of this constant has to stay within @c int
 
* boundaries.
 
*/
 
static const size_t FORMAT_FILE_ID_LENGTH = 3;
 
 
/**
 
* @brief Number of milliseconds to wait after a failed init/measurement
 
*/
 
static const unsigned long ERR_SEQ_DELAY_MS = 50;
 
 
/**
 
* @brief Number of milliseconds which each reconnection attempt to an sensor takes
 
*/
 
static const unsigned long ERR_PERIOD_MS = 3000;
 
 
/**
 
* @brief Threshold for how many sequenced errors until sensors transition to failure state
 
*/
 
static const unsigned char ERR_THRESHOLD = 3;
 
 
/**
 
* @brief Struct for connected sensors
 
*/
 
static task *tasks = NULL;
 
 
/**
 
* @brief Struct for connected sensors
 
*/
 
static size_t tasks_amt = 0;
 
 
/**
 
* @brief Struct for connected sensors
 
*/
 
static i2c_sensor *s_con = NULL;
 
 
/**
 
* @brief Amount of actually connected sensors
 
*/
 
static size_t s_con_amt = 0;
 
 
 
// Defined as "extern" in master.h.
 
phase active_phase;
 
 
 
int main(void) {
 
int rv = EXIT_SUCCESS; // return value for final exit
 
// int rv_f;  // return value of functions.
 
// int rv_l;  // return value for local sections
 
 
uid_t uid = 0;  // For root check.
 
baudrate baudrate = MAX_BAUDRATE;  // Should be highest by master supported I2C-frequency
 
task *task = NULL;
 
i2c_sensor *sensor = NULL, *sensor_height = NULL, *sensor_gps = NULL;
 
size_t sensor_name_length = 0;
 
size_t file_name_length = 0;
 
char *file_name = NULL;
 
ssize_t written = 0;  // For write() calls.
 
struct timespec cur_time, diff_time;
 
 
struct sigaction maintenance_action_default;
 
struct sigaction update_action_default;
 
struct sigaction maintenance_action;
 
struct sigaction update_action;
 
sigset_t all;
 
// TODO-DEL - Signal-Blocking while sensors running not useful in case of program freezing
 
// sigset_t normal;
 
 
const long WATCHDOG_KEEPALIVE_MS = 3000;
 
const char *const WATCHPUPPY_SIGNALING_CMD = "killall -SIGUSR1 watchpuppy.sh";
 
const long WATCHPUPPY_SIGNALING_CYCLE_S = 25; // needs to be smaller than TIMETOWAKEUP in watchpuppy.sh
 
 
const double CYCLE_LEN_MULT = 1.0; // To globaly manipulate cycle_len
 
 
// Constants for flight phase evaluation by height differences calculated by air pressure
 
 
// Rough false value filtering
 
const double HEIGHT_MIN = -500.0; // coast of dead sea
 
const double HEIGHT_MAX = 50000.0; // balloon ascension limit + some puffer due to height calculation formular error in higher heights
 
 
const unsigned char HEIGHT_AMT = 10; // Amount to collect, before calculate average vertical speed, false values included
 
const double V_VELO_HALT_TO_FLIGHT_THRESHOLD = 0.5; // Threshold in m/s
 
const uint16_t V_MOVEMENT_FILTER_MSK = 0xFFFF; // Bitmask to get last 16 Bits
 
const uint16_t V_MOVEMENT_LANDED_MSK = 0x0000; // Bitmask to check for enough detected sequential non-movements
 
const uint16_t  V_MOVEMENT_FLIGHING_MSK = V_MOVEMENT_FILTER_MSK; // Bitmask to check for enough detected sequential movements
 
 
double height_last = 0.0; // Last height value
 
double height_cur = 0.0; // Current height value
 
double height_cnt = 0.0; // Counter for all height values, also false values
 
double dheight_sum = 0.0; // Sum of delta height values
 
 
double v_velo = 0.0; // vertical velocity in m/s
 
uint16_t v_movement_seq = 0; // Bit counter for sequential vertical movement
 
 
const long MASTER_LED_MS = 500;
 
const long SENSORS_LED_MS = 100;
 
int master_LED_on = 0;
 
int sensors_LED_on = 0;
 
 
 
// Camera configurations
 
const camera_mode USB_CAMERA_MODE = PHOTO;
 
const char *USB_CAMERA_FILENAME = "imgs/camera_usb_bottom";
 
const camera_parameters USB_CAMERA_PARM = {
 
0,
 
10,    // freq, 1 picture per x seconds
 
// 1920, // width, too much CPU usage
 
// 1080, // height
 
// 1280, // width
 
// 720, // height
 
640, // width
 
480, // height
 
0
 
};
 
char usb_camera_is_active = 0;
 
 
const camera_mode RASPICAM_MODE = VIDEO;
 
const char *RASPICAM_FILENAME = "vids/raspicam_side";
 
const camera_parameters RASPICAM_PARM = {
 
3 * 60 * 60,   // duration, 3 hours, should be larger than flight time
 
0,
 
1920, // width
 
1080, // height
 
30    // framerate
 
};
 
char raspicam_is_active = 0;
 
 
const long GSM_SEND_MS = 180000;  // Every 3 minutes.
 
 
int tmp = 0;  // For unrelated use.
 
 
// FLOW #1
 
// Check for root privileges (bcm2835 doesn't do that automatically).
 
if ((uid = getuid()) && geteuid() == uid)
 
{
 
errno = EPERM;
 
fprintf(stderr, "%s:%i: root check failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR;
 
}
 
 
// FLOW #2.1
 
// Setup signal handling
 
// Maintenance handler
 
maintenance_action.sa_flags = 0;
 
sigemptyset(&maintenance_action.sa_mask);
 
sigaddset(&maintenance_action.sa_mask, MAINTENANCE_SIGNAL);
 
maintenance_action.sa_handler = sh_maintenance;
 
tmp = sigaction(MAINTENANCE_SIGNAL, &maintenance_action, &maintenance_action_default);
 
if (0 > tmp) {
 
fprintf(stderr, "%s:%d: setting maintenance handler failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR;
 
}
 
// FLOW #2.2
 
// Phase update handler
 
update_action.sa_flags = 0;
 
sigemptyset(&update_action.sa_mask);
 
sigaddset(&update_action.sa_mask, UPDATE_SIGNAL);
 
update_action.sa_handler = sh_update_phase;
 
tmp = sigaction(UPDATE_SIGNAL, &update_action, &update_action_default);
 
if (0 > tmp) {
 
fprintf(stderr, "%s:%d: setting phase update handler failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_SIG_MAINT;
 
}
 
// Mask to block all signals
 
tmp = sigfillset(&all);
 
if (-1 == tmp) {
 
fprintf(stderr, "%s:%i: filling blocking signal mask failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_SIG_UPDT;
 
}
 
 
// FLOW #3
 
// Try reading a previously saved phase or instead use and write START.
 
tmp = read_phase(&active_phase);
 
if (EXIT_FAILURE == tmp) {
 
active_phase = START;
 
tmp = write_phase(active_phase);
 
if (EXIT_FAILURE == tmp) {
 
fprintf(stderr, "%s:%i: writing initial phase failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_SIG_UPDT;
 
}
 
}
 
// FLOW #3.1
 
// Update v_movement_seq
 
if (FLIGHT == active_phase) {
 
v_movement_seq = 0xFF;
 
}
 
 
// FLOW #4.1
 
// bcm2835_init() comes with error message printing to stderr.
 
if (!bcm2835_init()) {
 
fprintf(stderr, "%s:%i: bcm2835_init failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_SIG_UPDT;
 
}
 
 
// FLOW #4.2
 
// Initialize I2C functionality.
 
if (!bcm2835_i2c_begin()) {
 
fprintf(stderr, "%s:%i: I2C initialization failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_BCM_LIB_CLOSE;
 
}
 
 
// FLOW #5
 
// Create and initialize array for connected sensors
 
bcm2835_i2c_set_baudrate(MIN_BAUDRATE);
 
if (EXIT_FAILURE == sensor_create_init_connected_sensors(SENSORS, SENSOR_AMT, &s_con, &s_con_amt)) {
 
fprintf(stderr, "%s:%i: sensor_create_connected_sensors failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_BCM_I2C_END;
 
}
 
 
// FLOW #6
 
// Determine greatest common I2C-frequency
 
for (size_t i = 0; s_con_amt > i; i++) {
 
if (NULL != s_con[i].reg) {
 
if (baudrate > s_con[i].reg->i2c_freq_max) {
 
baudrate = MIN_BAUDRATE;
 
}
 
}
 
}
 
bcm2835_i2c_set_baudrate(baudrate);
 
 
// FLOW #7.1
 
sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
 
if (NULL == sensor_height) {
 
fprintf(stderr, "%s:%d: no height sensor found. terminate program.\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_S_CON_DESTROY;
 
}
 
 
// FLOW #7.2
 
sensor_get_best_by_date_type(s_con, s_con_amt, POS_LAT, &sensor_gps);
 
if (NULL == sensor_gps) {
 
fprintf(stderr, "%s:%d: no GPS sensor found. Terminate program.\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_S_CON_DESTROY;
 
}
 
 
// FLOW #8.1
 
// Find length of longest sensor name to allocate sufficient memory for file names.
 
if (INT_MAX < FORMAT_FILE_ID_LENGTH) {
 
fprintf(stderr, "%s:%i: FORMAT_FILE_ID_LENGTH too large: expected 0..%i, got %zu\n", __FILE__, __LINE__, INT_MAX, FORMAT_FILE_ID_LENGTH);
 
rv = EXIT_FAILURE;
 
goto ERR_S_CON_DESTROY;
 
}
 
 
for (size_t i = 0; s_con_amt > i; ++i) {
 
sensor_name_length = strlen(s_con[i].reg->name);
 
if (sensor_name_length > file_name_length) {
 
file_name_length = sensor_name_length;
 
}
 
}
 
file_name_length += FORMAT_FILE_ID_LENGTH + 6;  // Length of "_###.csv\0".
 
file_name = calloc(file_name_length, sizeof (char));
 
if (NULL == file_name) {
 
fprintf(stderr, "%s:%i: calloc of %zu bytes failed\n", __FILE__, __LINE__, file_name_length * sizeof (char));
 
rv = EXIT_FAILURE;
 
goto ERR_S_CON_DESTROY;
 
}
 
 
// FLOW #8.2
 
// CSV-Preperations
 
for (size_t i = 0; s_con_amt > i; ++i) {
 
// Prepare filenames.
 
tmp = snprintf(file_name, file_name_length, "%s_%0*zu.csv", s_con[i].reg->name, (int) FORMAT_FILE_ID_LENGTH, i);
 
if (0 > tmp) {
 
fprintf(stderr, "%s:%i: buffering file name for %s failed\n", __FILE__, __LINE__, s_con[i].reg->name);
 
rv = EXIT_FAILURE;
 
goto ERR_FILE_NAME_FREE;
 
}
 
 
// Open files.
 
tmp = access(file_name, F_OK);  // Check if file exists (for CSV header).
 
s_con[i].csv_file = open(file_name, O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
 
if (0 > s_con[i].csv_file) {
 
fprintf(stderr, "%s:%i: opening file %s failed: %s\n", __FILE__, __LINE__, file_name, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_FILE_NAME_FREE;
 
}
 
// If file did not exist, write header.
 
if (tmp) {
 
errno = 0;
 
written = write(s_con[i].csv_file, s_con[i].reg->csv_header, strlen(s_con[i].reg->csv_header));
 
if (0 > written) {
 
fprintf(stderr, "%s:%d: writing CSV header failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_FILE_NAME_FREE;
 
}
 
}
 
}
 
 
// FLOW #9.1
 
// Create tasks
 
tasks_amt = s_con_amt + TASK_TYPE_AMT - 1;
 
tasks = malloc(tasks_amt * sizeof (struct task));
 
if (NULL == tasks) {
 
fprintf(stderr, "%s:%i: malloc of %zu bytes failed\n", __FILE__, __LINE__, file_name_length * sizeof (char));
 
rv = EXIT_FAILURE;
 
goto ERR_FILE_NAME_FREE;
 
}
 
// FLOW #9.2
 
// Init tasks
 
// Sensors
 
for (size_t i = 0; s_con_amt > i; i++) {
 
tasks[i].type = SENSOR;
 
tasks[i].state = RUNNING;
 
tasks[i].next_time = (struct timespec) {0, 0};
 
tasks[i].p = &(s_con[i]);
 
sensor = (i2c_sensor *) (tasks[i].p);
 
tasks[i].active_phases = sensor->reg->active_phases;
 
s_con[i].t = &(tasks[i]);
 
 
}
 
// LED tasks
 
tasks[s_con_amt].type = LED_MASTER;
 
tasks[s_con_amt].state = RUNNING;
 
tasks[s_con_amt].active_phases = START | ASCEND;
 
errno = 0;
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt].next_time))) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
tasks[s_con_amt + 1].type = LED_SENSORS;
 
tasks[s_con_amt + 1].state = RUNNING;
 
tasks[s_con_amt + 1].active_phases = START | ASCEND;
 
errno = 0;
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 1].next_time))) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
// Fallback task for state change, if sensors or state transitions fail
 
tasks[s_con_amt + 2].type = FALLBACK_STATE_TO_RECOVERY_CHANGER;
 
tasks[s_con_amt + 2].state = RUNNING;
 
tasks[s_con_amt + 2].active_phases = START | FLIGHT | RECOVERY;
 
tasks[s_con_amt + 2].next_time = (struct timespec) {0, 0};
 
tasks[s_con_amt + 2].p = NULL;
 
// Camera USB task
 
tasks[s_con_amt + 3].type = CAM_USB_TASK;
 
tasks[s_con_amt + 3].state = RUNNING;
 
tasks[s_con_amt + 3].active_phases = START | FLIGHT | RECOVERY;
 
tasks[s_con_amt + 3].next_time = (struct timespec) {0, 0};
 
// Camera raspi task
 
tasks[s_con_amt + 4].type = CAM_RASPI_TASK;
 
tasks[s_con_amt + 4].state = RUNNING;
 
tasks[s_con_amt + 4].active_phases = START | FLIGHT | RECOVERY;
 
tasks[s_con_amt + 4].next_time = (struct timespec) {0, 0};
 
// GSM sender task
 
tasks[s_con_amt + 5].type = GSM_TASK;
 
tasks[s_con_amt + 5].state = RUNNING;
 
tasks[s_con_amt + 5].active_phases = START | FLIGHT | RECOVERY;
 
errno = 0;
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 5].next_time))) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
// Watchdog task
 
tasks[s_con_amt + 6].type = WATCHDOG;
 
tasks[s_con_amt + 6].state = RUNNING;
 
tasks[s_con_amt + 6].active_phases = START | FLIGHT | RECOVERY;
 
errno = 0;
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 6].next_time))) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
// Watchpuppy task
 
tasks[s_con_amt + 7].type = WATCHPUPPY;
 
tasks[s_con_amt + 7].state = RUNNING;
 
tasks[s_con_amt + 7].active_phases = START | FLIGHT | RECOVERY;
 
errno = 0;
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 7].next_time))) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
 
// FLOW #10
 
// Update states by phases
 
if (EXIT_FAILURE == task_update_states_by_phase(tasks, tasks_amt, active_phase)) {
 
fprintf(stderr, "%s:%i: task_update_states_by_phase failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
 
// Main loop
 
while (QUIT != active_phase && EXIT_SUCCESS == task_get_next(tasks, tasks_amt, &task)) {
 
 
// Is task ready? If not, sleep remaining time
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
if (timespec_gt(task->next_time, cur_time)) {
 
diff_time = timespec_sub(task->next_time, cur_time);
 
do {
 
errno = 0;
 
} while (0 > clock_nanosleep(CLOCK_MONOTONIC, 0,&diff_time, &diff_time) && EINTR == errno);
 
}
 
 
switch (task->type) {
 
case SENSOR:
 
 
sensor = (i2c_sensor*) (task->p);
 
// Sensor measurement cycle
 
// Repeat, as long as sensor isn't sleeping and there's no delay
 
do {
 
 
// TODO-DEL - Signal-Blocking while sensors running not useful in case of program freezing
 
// // Block all signals.
 
// tmp = sigprocmask(SIG_BLOCK, &all, &normal);
 
// if (-1 == tmp) {
 
// fprintf(stderr, "%s:%i: setting blocking signal mask failed\n", __FILE__, __LINE__);
 
// }
 
 
switch (sensor->state) {
 
 
case INIT:
 
if (EXIT_FAILURE == sensor->reg->init(active_phase, &(sensor->it_rem), &(sensor->processing_time))) { // Init_Err
 
fprintf(stderr, "%s:%i: init for %s failed\n", __FILE__, __LINE__, sensor->reg->name);
 
sensor->err_seq++;
 
sensor->err_total++;
 
if (ERR_THRESHOLD <= sensor->err_seq) {
 
sensor->state = FAILURE;
 
sensor->failure_total++;
 
if (sensor == sensor_height) {
 
sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
 
}
 
sensor->processing_time = ERR_PERIOD_MS;
 
} else {
 
sensor->processing_time = ERR_SEQ_DELAY_MS;
 
}
 
} else { // Init_Succ
 
sensor->err_seq = 0;
 
if (0 < sensor->processing_time) {
 
// Update next_time
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
task->next_time = timespec_add_ms(cur_time, sensor->processing_time);
 
}
 
if (0 < sensor->it_rem) { // Init_Unfinished
 
sensor->it_rem--;
 
} else { // Init_Finished
 
sensor->state = MEASURE;
 
sensor->it_rem = -1;
 
// Set sensor->next_cycle_time
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &(sensor->next_cycle_time))) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
sensor->next_cycle_time = timespec_add_ms(sensor->next_cycle_time, sensor->reg->cycle_len * CYCLE_LEN_MULT);
 
}
 
}
 
break;
 
case MEASURE:
 
if (EXIT_FAILURE == sensor->reg->measure(active_phase, &(sensor->measurements), &(sensor->it_rem), &(sensor->processing_time))) { // Measure_Err
 
fprintf(stderr, "%s:%i: measure for %s failed\n", __FILE__, __LINE__, sensor->reg->name);
 
sensor->err_seq++;
 
sensor->err_total++;
 
if (ERR_THRESHOLD <= sensor->err_seq) {
 
sensor->state = FAILURE;
 
sensor->failure_total++;
 
if (sensor == sensor_height) {
 
sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
 
}
 
sensor->processing_time = 0;
 
} else {
 
sensor->processing_time = ERR_SEQ_DELAY_MS;
 
}
 
} else { // Measure_Succ
 
sensor->err_seq = 0;
 
if (0 < sensor->it_rem) { // Measure_Unfinished
 
sensor->it_rem--;
 
if (0 < sensor->processing_time) {
 
// Update next_time
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
task->next_time = timespec_add_ms(cur_time, sensor->processing_time);
 
}
 
} else { // Measure_Finished
 
sensor->it_rem = -1;
 
// TODO-UC
 
// task->next_time = timespec_add_ms(sensor->next_cycle_time, sensor->processing_time);
 
// sensor->next_cycle_time = timespec_add_ms(sensor->next_cycle_time, sensor->reg->cycle_len * CYCLE_LEN_MULT);
 
 
// TODO-TEST-2-B
 
if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
 
fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
task->next_time = timespec_add_ms(cur_time, sensor->processing_time);
 
sensor->next_cycle_time = timespec_add_ms(sensor->next_cycle_time, sensor->reg->cycle_len * CYCLE_LEN_MULT);
 
if (timespec_gt(sensor->next_cycle_time, task->next_time)) {
 
task->next_time = sensor->next_cycle_time;
 
} else {
 
fprintf(stderr, "%s:%i: %ld.%03ld: sensor %s cycle_len exceeded\n", __FILE__, __LINE__, cur_time.tv_sec, cur_time.tv_nsec / 1000000, sensor->reg->name);
 
sensor->next_cycle_time = task->next_time;
 
}
 
// TODO-TEST-2-E
 
 
sensor->processing_time = 1; // Actual value doesn't matter, just to stop sensor loop
 
if (EXIT_FAILURE == measurement_append_to_csv_file(sensor->csv_file, sensor->measurements)) {
 
fprintf(stderr, "%s:%i: append_to_csv_file for %s's data failed\n", __FILE__, __LINE__, sensor->reg->name);
 
}
 
// Test for phase switching conditions.
 
if (sensor_height == sensor) { // New height value?
 
// TODO-MV - move to sensor_height probing an save in pointer
 
for (size_t i = 0; sensor->reg->date_amt > i; i++) { // get height
 
if (HEIGHT == sensor->reg->types[i]) {
 
height_cur = sensor->measurements.data[i];
 
break;
 
}
 
}
 
if (HEIGHT_MIN < height_cur || HEIGHT_MAX > height_cur) { // No false value?
 
dheight_sum += height_cur - height_last;
 
height_last = height_cur;
 
}
 
height_cnt++;
 
if (HEIGHT_AMT == height_cnt) { // Enough values collected? Calculate vert. velocity
 
v_velo = dheight_sum / height_cnt / (double) sensor->reg->cycle_len * 1000;
 
height_cnt = 0.0;
 
dheight_sum = 0.0;
 
 
if (v_velo > V_VELO_HALT_TO_FLIGHT_THRESHOLD || v_velo < (-1 * V_VELO_HALT_TO_FLIGHT_THRESHOLD)) { // Threshold to detect movement exceeded?
 
v_movement_seq = v_movement_seq << 1 | 0x01;
 
if (START == active_phase && V_MOVEMENT_FLIGHING_MSK == (v_movement_seq & V_MOVEMENT_FILTER_MSK)) { // In START pahse and enough movements detected?
 
printf("Phase switched to FLIGHT!\n");
 
active_phase = FLIGHT;
 
tmp = write_phase(active_phase);
 
if (EXIT_FAILURE == tmp) {
 
fprintf(stderr, "%s:%i: writing updated phase failed: active_phase=%u\n", __FILE__, __LINE__, active_phase);
 
}
 
// Update states by phases
 
if (EXIT_FAILURE == task_update_states_by_phase(tasks, tasks_amt, active_phase)) {
 
fprintf(stderr, "%s:%i: task_update_states_by_phase failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
}
 
} else { // Thresehold not exceeded
 
v_movement_seq = (v_movement_seq << 1) | 0x0000;
 
if (FLIGHT == active_phase && V_MOVEMENT_LANDED_MSK == (v_movement_seq & V_MOVEMENT_FILTER_MSK)) { // Enough stationaries detected?
 
printf("Phase switched to RECOVERY!\n");
 
active_phase = RECOVERY;
 
tmp = write_phase(active_phase);
 
if (EXIT_FAILURE == tmp) {
 
fprintf(stderr, "%s:%i: writing updated phase failed: active_phase=%u\n", __FILE__, __LINE__, active_phase);
 
}
 
// Update states by phases
 
if (EXIT_FAILURE == task_update_states_by_phase(tasks, tasks_amt, active_phase)) {
 
fprintf(stderr, "%s:%i: task_update_states_by_phase failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
}
 
}
 
// printf("s_name = %s\nheight_cur = %lf\nv_velo = %lf\nv_movement_seq = 0x%04x\n\n", sensor->reg->name, height_cur, v_velo, v_movement_seq); // TODO-DEL
 
}
 
}
 
}
 
}
 
break;
 
case FAILURE:
 
if (EXIT_FAILURE == sensor->reg->is_connected()) { // Sensor still dead
 
fprintf(stderr, "%s:%i: reconnect for %s failed\n", __FILE__, __LINE__, sensor->reg->name);
 
sensor->err_total++;
 
sensor->processing_time = ERR_PERIOD_MS;
 
task->next_time = timespec_add_ms(task->next_time, sensor->processing_time);
 
} else { // Sensor recovered
 
fprintf(stderr, "%s:%i: reconnect for %s successful\n", __FILE__, __LINE__, sensor->reg->name);
 
sensor->err_seq = 0;
 
sensor->state = INIT;
 
sensor->it_rem = -1;
 
if (EXIT_SUCCESS == sensor_has_date_type(*sensor, HEIGHT)) {
 
sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
 
}
 
sensor->processing_time = 0;
 
}
 
break;
 
default:
 
fprintf(stderr, "%s:%i: sensor \"%s\" has illegal state: %d. Stopped sensor task\n", __FILE__, __LINE__, sensor->reg->name, sensor->state);
 
task->state = STOPPED;
 
}
 
// TODO-DEL - Signal-Blocking while sensors running not useful in case of program freezing
 
// // Reset signal mask.
 
// tmp = sigprocmask(SIG_SETMASK, &normal, NULL);
 
// if (-1 == tmp) {
 
// fprintf(stderr, "%s:%i: returning to normal signal mask failed\n", __FILE__, __LINE__);
 
// }
 
} while (SLEEP != sensor->state && 0 >= sensor->processing_time);
 
break;
 
 
case LED_MASTER:
 
if (START == active_phase) {
 
// Blinks if master is running.
 
master_LED_on = !master_LED_on;
 
set_taskLED(task->type, master_LED_on);
 
 
task->next_time = timespec_add_ms(task->next_time, MASTER_LED_MS);
 
} else {
 
set_taskLED(task->type, 0);
 
task->state = STOPPED;
 
}
 
break;
 
 
case LED_SENSORS:
 
if (START == active_phase) {
 
// Count current failures.
 
tmp = 0;
 
for (size_t i = 0; i < s_con_amt; ++i) {
 
if (FAILURE == s_con[i].state) {
 
++tmp;
 
}
 
}
 
 
// Disconnected sensors or current failures -> off.
 
if (SENSOR_AMT != s_con_amt || tmp) {
 
sensors_LED_on = 0;
 
// Any past failures -> blinking; no failures at all -> on.
 
} else {
 
tmp = 0;
 
for (size_t i = 0; i < s_con_amt; ++i) {
 
tmp += s_con[i].failure_total;
 
}
 
sensors_LED_on = tmp ? !sensors_LED_on : 1;
 
}
 
set_taskLED(task->type, sensors_LED_on);
 
 
task->next_time = timespec_add_ms(task->next_time, SENSORS_LED_MS);
 
} else {
 
set_taskLED(task->type, 0);
 
task->state = STOPPED;
 
}
 
break;
 
 
case CAM_RASPI_TASK:
 
switch (active_phase) {
 
case RECOVERY:
 
if (raspicam_is_active) {
 
CAMERAS[0].stop();
 
raspicam_is_active = 0;
 
}
 
break;
 
default: // START, FLIGHT, etc
 
if (!raspicam_is_active) {
 
CAMERAS[0].start(RASPICAM_MODE, RASPICAM_FILENAME, RASPICAM_PARM);
 
raspicam_is_active = 1;
 
}
 
}
 
task->state = STOPPED;
 
break;
 
 
case CAM_USB_TASK:
 
switch (active_phase) {
 
case RECOVERY:
 
if (raspicam_is_active) {
 
CAMERAS[1].stop();
 
usb_camera_is_active = 0;
 
}
 
break;
 
default: // START, FLIGHT, etc
 
if (!usb_camera_is_active) {
 
CAMERAS[1].start(USB_CAMERA_MODE, USB_CAMERA_FILENAME, USB_CAMERA_PARM);
 
usb_camera_is_active = 1;
 
}
 
}
 
task->state = STOPPED;
 
break;
 
 
case GSM_TASK:
 
// send_GSM(&sensor_gps->measurements); // TODO-UC
 
task->next_time = timespec_add_ms(task->next_time, GSM_SEND_MS);
 
break;
 
 
case FALLBACK_STATE_TO_RECOVERY_CHANGER:
 
if (EXIT_FAILURE == fallback_recovery_task(task, &active_phase, cur_time)) {
 
fprintf(stderr, "%s:%s:%i: fallback_recovery_task failed\n", __FILE__, __func__, __LINE__);
 
rv = EXIT_FAILURE;
 
goto ERR_TASKS_FREE;
 
}
 
break;
 
 
case WATCHDOG:
 
wake_watchdog();
 
task->next_time = timespec_add_ms(task->next_time, WATCHDOG_KEEPALIVE_MS);
 
break;
 
 
case WATCHPUPPY:
 
system(WATCHPUPPY_SIGNALING_CMD);
 
task->next_time = timespec_add_ms(task->next_time, WATCHPUPPY_SIGNALING_CYCLE_S * 1000);
 
break;
 
 
default:
 
fprintf(stderr, "%s:%i: task with faulty task_type\n", __FILE__, __LINE__);
 
}
 
}
 
 
// Cleanup in reverse order.
 
ERR_TASKS_FREE:
 
free(tasks);
 
ERR_FILE_NAME_FREE:
 
free(file_name);
 
ERR_S_CON_DESTROY:
 
for (size_t i = 0; s_con_amt > i; i++) {
 
measurement_destroy(&(s_con[i].measurements));
 
}
 
free(s_con);
 
ERR_BCM_I2C_END:
 
bcm2835_i2c_end();
 
ERR_BCM_LIB_CLOSE:
 
if (!bcm2835_close()) {
 
rv = EXIT_FAILURE;
 
}
 
ERR_SIG_UPDT:
 
tmp = sigaction(UPDATE_SIGNAL, &update_action_default, NULL);
 
if (0 > tmp) {
 
fprintf(stderr, "%s:%d: resetting phase update handler to default failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
}
 
ERR_SIG_MAINT:
 
tmp = sigaction(MAINTENANCE_SIGNAL, &maintenance_action_default, NULL);
 
if (0 > tmp) {
 
fprintf(stderr, "%s:%d: resetting maintenance handler to default failed\n", __FILE__, __LINE__);
 
rv = EXIT_FAILURE;
 
}
 
ERR:
 
return rv;
 
}
 
 
 
int read_phase(phase *const p) {
 
FILE *file = NULL;
 
int tmp = 0;
 
int rv = EXIT_SUCCESS;
 
 
if (NULL == p) {
 
fprintf(stderr, "%s:%i: no phase given\n", __FILE__, __LINE__);
 
return EXIT_FAILURE;
 
}
 
 
/* Automated creation not specified in header.
 
// File doesn't exist.
 
tmp = access(file_name, F_OK);
 
if (tmp) {
 
*p = START;
 
return write_phase(*p);
 
}
 
*/
 
 
file = fopen(PERSISTENCE_PATH, "r");
 
if (NULL == file) {
 
fprintf(stderr, "%s:%i: opening persistence file for reading failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
 
errno = 0;
 
tmp = fscanf(file, "%u", p);
 
if (1 != tmp) {
 
fprintf(stderr, "%s:%i: scanning for phase failed: %s\n", __FILE__, __LINE__, 0 == errno ? "no value" : strerror(errno));
 
rv = EXIT_FAILURE;
 
}
 
 
tmp = fclose(file);
 
if (tmp) {
 
fprintf(stderr, "%s:%i: closing persistence file after reading failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
 
return rv;
 
}
 
 
 
int write_phase(phase p) {
 
FILE *file = NULL;
 
int tmp = 0;
 
 
file = fopen(PERSISTENCE_PATH, "w");
 
if (NULL == file) {
 
fprintf(stderr, "%s:%i: opening persistence file for writing failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
 
tmp = fprintf(file, "%i", p);
 
if (0 >= tmp) {
 
fprintf(stderr, "%s:%i: printing phase failed\n", __FILE__, __LINE__);
 
return EXIT_FAILURE;
 
}
 
 
tmp = fclose(file);
 
if (tmp) {
 
fprintf(stderr, "%s:%i: closing persistence file after writing failed: %s\n", __FILE__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
 
return EXIT_SUCCESS;
 
}
 
 
 
 
void sh_maintenance(int sig) {
 
unsigned long total_failures = 0;
 
size_t in_failure = 0;
 
 
size_t i_all = 0;
 
 
if (MAINTENANCE_SIGNAL == sig) {
 
// Collect combined metrics.
 
for (size_t i = 0; i < s_con_amt; ++i) {
 
total_failures += s_con[i].failure_total;
 
if (FAILURE == s_con[i].state) {
 
++in_failure;
 
}
 
}
 
 
// TODO-IMPL - Show master status as following:
 
// Master-Status CRITICAL, all height sensors in FAILURE, or no height sensor
 
printf("\n[%s][%s] %-10s (", 0 == total_failures ? "\033[0;92m GOOD \033[0m" : (0 == in_failure ? "\033[0;93m OKAY \033[0m" : "\033[0;33m ALRM \033[0m"), 0 < s_con_amt ? "\033[0;36mACTIVE\033[0m" : "\033[0;30m DEAD \033[0m", "master");
 
switch (active_phase) {
 
// Single phases.
 
case START:
 
printf("START");
 
break;
 
case ASCEND:
 
printf("ASCEND");
 
break;
 
case DESCEND:
 
printf("DESCEND");
 
break;
 
case RECOVERY:
 
printf("RECOVERY");
 
break;
 
// Combined phases (name strings ending with '*').
 
case FLIGHT:
 
printf("FLIGHT*");
 
break;
 
default:
 
printf("\033[0;90m?active_phase=%u?\033[0m", active_phase);
 
}
 
printf(" phase, %zu/%zu sensors connected, %lu failures total, %zu sensors in FAILURE)\n\n", s_con_amt, SENSOR_AMT, total_failures, in_failure);
 
 
printf("[ COND ][  TASK  ][STATE ]    NAME    (ERROR STATISTICS)\n");
 
for (size_t i = 0; i < s_con_amt; ++i) {
 
printf("[%s][%s][%s] %-10s (%lu failures total, %lu errors total, %u erorrs in sequence)\n",
 
0 == s_con[i].err_total ? "\033[0;92m GOOD \033[0m" : (FAILURE == s_con[i].state ? "\033[0;91m FAIL \033[0m" : (0 == s_con[i].err_seq ? "\033[0;33m OKAY \033[0m" : "\033[0;33mINTRUP\033[0m")),
 
RUNNING == s_con[i].t->state ? "\033[0;92m RUNNING \033[0m" : "\033[0;34m STOPPED \033[0m",
 
INIT == s_con[i].state ? "\033[0;95m INIT \033[0m" : (!s_con[i].reg->active_phases ? "\033[0;35mHYBERN\033[0m" : (s_con[i].reg->active_phases & active_phase ? "\033[0;36mACTIVE\033[0m" : "\033[0;34mASLEEP\033[0m")),
 
s_con[i].reg->name, s_con[i].failure_total, s_con[i].err_total, s_con[i].err_seq);
 
}
 
if (SENSOR_AMT > s_con_amt) {
 
for (size_t i_con = 0; SENSOR_AMT > i_all && i_con < s_con_amt; ++i_all) {
 
if (&SENSORS[i_all] == s_con[i_con].reg) {
 
++i_con;
 
} else {
 
printf("[\033[0;30mDISCON\033[0m][      ] %-10s\n", SENSORS[i_all].name);
 
}
 
}
 
for (; SENSOR_AMT > i_all; ++i_all) {
 
printf("[\033[0;30mDISCON\033[0m][      ] %-10s\n", SENSORS[i_all].name);
 
}
 
}
 
 
fflush(stdout);
 
}
 
}
 
 
void sh_update_phase(int sig) {
 
sigset_t all;
 
sigset_t normal;
 
int tmp = 0;
 
 
tmp = sigfillset(&all);
 
if (-1 == tmp) {
 
fprintf(stderr, "%s:%i: filling blocking signal mask failed\n", __FILE__, __LINE__);
 
}
 
tmp = sigprocmask(SIG_BLOCK, &all, &normal);
 
if (-1 == tmp) {
 
fprintf(stderr, "%s:%i: setting blocking signal mask failed\n", __FILE__, __LINE__);
 
}
 
 
if (UPDATE_SIGNAL == sig) {
 
tmp = read_phase(&active_phase);
 
if (EXIT_FAILURE == tmp) {
 
fprintf(stderr, "%s:%i: reading phase on signal failed\n", __FILE__, __LINE__);
 
}
 
tmp = task_update_states_by_phase(tasks, tasks_amt, active_phase);
 
if (EXIT_FAILURE == tmp) {
 
fprintf(stderr, "%s:%s:%i: task_update_states_by_phase failed\n", __FILE__, __func__, __LINE__);
 
}
 
}
 
 
tmp = sigprocmask(SIG_SETMASK, &normal, NULL);
 
if (-1 == tmp) {
 
fprintf(stderr, "%s:%i: returning to normal signal mask failed\n", __FILE__, __LINE__);
 
}
 
}
 
 
 
int fallback_recovery_task(task *thiz, phase *cur_phase, struct timespec cur_time) {
 
FILE *file = NULL;
 
int rv = EXIT_SUCCESS;
 
int rv_f = 0;
 
 
if (NULL == thiz) {
 
fprintf(stderr, "%s:%s:%i: Input thiz is NULL\n", __FILE__, __func__, __LINE__);
 
return EXIT_FAILURE;
 
}
 
if (NULL == cur_phase) {
 
fprintf(stderr, "%s:%s:%i: Input cur_phase is NULL\n", __FILE__, __func__, __LINE__);
 
return EXIT_FAILURE;
 
}
 
 
switch (*cur_phase) {
 
case START: // Set next_time and save it
 
// Set next_time
 
thiz->next_time.tv_sec = cur_time.tv_sec + MAX_FLIGHT_TIME_S;
 
thiz->next_time.tv_nsec = cur_time.tv_nsec;
 
// Write next_time to file
 
file = fopen(MAX_FLIGHT_TIME_PATH, "w");
 
if (NULL == file) {
 
fprintf(stderr, "%s:%s:%i: fopen failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
rv_f = fprintf(file, "%ld\n%ld", thiz->next_time.tv_sec, thiz->next_time.tv_nsec);
 
if (0 >= rv_f) {
 
fprintf(stderr, "%s:%s:%i: fprintf failed and returned %d\n", __FILE__, __func__, __LINE__, rv_f);
 
rv = EXIT_FAILURE;
 
}
 
rv_f = fclose(file);
 
if (EOF == rv_f) {
 
fprintf(stderr, "%s:%s:%i: fclose failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
break;
 
 
case RECOVERY: // Stopp own task
 
thiz->state = STOPPED;
 
break;
 
 
default: // Check for passed MAX_FLIGHT_TIME
 
// Is next_time not set?
 
if (0 == thiz->next_time.tv_sec && 0 == thiz->next_time.tv_nsec) {
 
// Read next_time from file
 
file = fopen(MAX_FLIGHT_TIME_PATH, "r");
 
if (NULL == file) {
 
fprintf(stderr, "%s:%s:%i: fopen failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
rv_f = fscanf(file, "%ld\n%ld", &(thiz->next_time.tv_sec), &(thiz->next_time.tv_nsec));
 
errno = 0;
 
if (2 != rv_f) {
 
fprintf(stderr, "%s:%i: fscanf failed: %s\n", __FILE__, __LINE__, 0 == errno ? "faulty file content" : strerror(errno));
 
rv = EXIT_FAILURE;
 
}
 
rv_f = fclose(file);
 
if (EOF == rv_f) {
 
fprintf(stderr, "%s:%s:%i: fclose failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
 
return EXIT_FAILURE;
 
}
 
}
 
// Is MAX_FLIGHT_TIME reached?
 
if (timespec_lt(thiz->next_time, cur_time)) {
 
fprintf(stderr, "%s:%s:%i: MAX_FLIGHT_TIME_S = %ld s reached. Switched state to RECOVERY.\n", __FILE__, __func__, __LINE__, MAX_FLIGHT_TIME_S);
 
*cur_phase = RECOVERY;
 
// Update phase
 
rv_f = task_update_states_by_phase(tasks, tasks_amt, active_phase);
 
if (EXIT_FAILURE == rv_f) {
 
fprintf(stderr, "%s:%s:%i: task_update_states_by_phase failed\n", __FILE__, __func__, __LINE__);
 
return EXIT_FAILURE;
 
}
 
rv_f = write_phase(*cur_phase);
 
if (EXIT_FAILURE == rv_f) {
 
fprintf(stderr, "%s:%s:%i: write_phase failed\n", __FILE__, __func__, __LINE__);
 
return EXIT_FAILURE;
 
}
 
// Stopp own task
 
thiz->state = STOPPED;
 
}
 
}
 
 
return rv;
 
}
 
</syntaxhighlight>
 
</div>
 
</div>
 
  
 
== Auszüge aus dem Quellcode ==
 
== Auszüge aus dem Quellcode ==

Version vom 18. März 2020, 13:51 Uhr

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.

Auszüge aus dem Quellcode

Nach dem in der Designphase festgeleten Mustern wurde der Master, die Sensoren sowie die Tasks wie folgt implementiert:

master.h
/**
 * @file  master.h
 * @brief Definitions for the Master program.
 *
 * @author Jonas Gaida, Florian Schwarz
 */

#ifndef INCLUDE_MASTER_H_
#define INCLUDE_MASTER_H_

#include "sensor.h"

#include "sensor_gyml8511.h"
#include "sensor_mcp9808.h"
#include "sensor_mpu9250.h"
#include "sensor_ms5611_out.h"
#include "sensor_ms8607_out.h"
#include "sensor_ms8607_in.h"
#include "sensor_sht31d_out.h"
#include "sensor_sht35_out.h"
#include "sensor_sht35_in.h"
#include "sensor_tmp117.h"
#include "sensor_camm8.h"

#include "sensor_height_sim.h" // Simulates flight height data

#include "sensor_buzzer.h"

#include "camera_raspicam.h"
#include "camera_usb.h"


/**
 * @brief Array of all registered sensors.
 *
 * This needs to be manually filled.
 */
const i2c_sensor_reg SENSORS[] = {
	MS8607_OUT,
	MS5611_OUT,
	MS8607_IN,
	SHT35_OUT,
	SHT31D_OUT,
	SHT35_IN,
	TMP117,
	MCP9808,
	MPU9250,
	GYML8511,
	CAMM8,
	HEIGHT_SIM,
	BUZZER
};


/**
 * @brief Constant for easy access to the total number of registered sensors.
 */
const size_t SENSOR_AMT = sizeof SENSORS / sizeof (i2c_sensor_reg);


/**
 * @brief Array of all cameras.
 *
 * This needs to be manually filled.
 */
const camera CAMERAS[] = {
	RASPICAM,
	USBCAM
};


/**
 * @brief Constant for easy access to the total number of cameras.
 */
const size_t CAMERA_AMT = sizeof CAMERAS / sizeof (camera);


/**
 * @brief Path of the persistence file for the phase.
 */
const char *const PERSISTENCE_PATH = "./phase";



/**
 * @brief The phase in which the master currently is.
 */
extern phase active_phase;



int main(void);


// Signal handler functions

/**
 * @brief Reads the phase saved in the file at PERSISTENCE_PATH.
 *
 * This method only reads if the file exists. Otherwise it does not create it
 * with a default phase. Instead it just returns unsuccessfully.
 *
 * @param[out] p Pointer to a phase which will be filled.
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 *
 * @sa PERSISTENCE_PATH
 */
int read_phase(phase *const p);


/**
 * @brief Writes the given phase to the file at PERSISTENCE_PATH.
 *
 * @param[in] p The phase to be written.
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 *
 * @sa PERSISTENCE_PATH
 */
int write_phase(phase p);


/**
 * @brief Checks the health running master.
 *
 * Implements the function type required by a signal handler.
 *
 * @param[in] sig The signal which triggered the handler call.
 */
void sh_maintenance(int sig);


/**
 * @brief Forces the active phase to be re-read.
 *
 * Implements the function type required by a signal handler.
 *
 * @param[in] sig The signal which triggered the handler call.
 *
 * @sa read_phase, active_phase
 */
void sh_update_phase(int sig);


/**
 * @brief Contains logic for the FALLBACK_STATE_TO_RECOVERY_CHANGER task
 *
 * Ensures that master switches to RECOVERY phase after MAX_FLIGHT_TIME.
 * To withstand forced reboot, this task writes its time for phase change to a file specified by MAX_FLIGHT_TIME_PATH
 *
 * @param[in,out] thiz pointer to own task struct
 * @param[in,out] cur_phase pointer to current active phase
 * @param[in] cur_time current time
 *
 */
int fallback_recovery_task(task *thiz, phase *cur_phase, struct timespec cur_time);
#endif  // INCLUDE_MASTER_H_
master.c
/**
 * @file  master.c
 * @brief Implementation of master.h
 *
 * @author Jonas Gaida, Florian Schwarz
 */

#include "master.h"

#include <stdlib.h>  // free, exit codes
#include <unistd.h>  // calloc
#include <stdio.h>  // fprintf, fflush, stdout, stderr
#include <fcntl.h>  // open
#include <sys/stat.h>  // open flags
#include <signal.h>  // struct sigaction, sigaction
#include <string.h>  // strerror, strlen
#include <time.h>  // clock_gettime
#include <limits.h>  // INT_MAX
#include <errno.h>  // errno

#include "bcm2835.h"
#include "timespec_helper.h"
#include "task.h"
#include "task_led.h"
#include "task_gsm.h"
#include "task_watchdog.h"



/**
 * @brief Signal which triggers the sh_maintenance function.
 *
 * @sa sh_maintenance
 */
static const int MAINTENANCE_SIGNAL = SIGUSR1;

/**
 * @brief Signal which triggers the interaction function.
 *
 * @sa interaction
 */
static const int UPDATE_SIGNAL = SIGUSR2;


/**
 * @brief Maximum time in s until state change to RECOVERY
 *
 * Fallback mechanism, to guarantee final RECOVERY mode.
 */
static const unsigned long MAX_FLIGHT_TIME_S = 3 * 60 * 60; // 3 hours
/**
 * @brief Stores time for phase change to RECOVERY
 */
const char *const MAX_FLIGHT_TIME_PATH = "./max_flight_time.pers";

/**
 * @brief Length of the ID suffix of each sensor's file name.
 *
 * This is used in a @c printf call as a '*' length field. These expect @c int
 * values. Therefor the value of this constant has to stay within @c int
 * boundaries.
 */
static const size_t FORMAT_FILE_ID_LENGTH = 3;

/**
 * @brief Number of milliseconds to wait after a failed init/measurement
 */
static const unsigned long ERR_SEQ_DELAY_MS = 50;

/**
 * @brief Number of milliseconds which each reconnection attempt to an sensor takes
 */
static const unsigned long ERR_PERIOD_MS = 3000;

/**
 * @brief Threshold for how many sequenced errors until sensors transition to failure state
 */
static const unsigned char ERR_THRESHOLD = 3;

/**
 * @brief Struct for connected sensors
 */
static task *tasks = NULL;

/**
 * @brief Struct for connected sensors
 */
static size_t tasks_amt = 0;

/**
 * @brief Struct for connected sensors
 */
static i2c_sensor *s_con = NULL;

/**
 * @brief Amount of actually connected sensors
 */
static size_t s_con_amt = 0;


// Defined as "extern" in master.h.
phase active_phase;


int main(void) {
	int rv = EXIT_SUCCESS; // return value for final exit
	//	int rv_f;  // return value of functions.
	//	int rv_l;  // return value for local sections

	uid_t uid = 0;  // For root check.
	baudrate baudrate = MAX_BAUDRATE;  // Should be highest by master supported I2C-frequency
	task *task = NULL;
	i2c_sensor *sensor = NULL, *sensor_height = NULL, *sensor_gps = NULL;
	size_t sensor_name_length = 0;
	size_t file_name_length = 0;
	char *file_name = NULL;
	ssize_t written = 0;  // For write() calls.
	struct timespec cur_time, diff_time;

	struct sigaction maintenance_action_default;
	struct sigaction update_action_default;
	struct sigaction maintenance_action;
	struct sigaction update_action;
	sigset_t all;
	// TODO-DEL - Signal-Blocking while sensors running not useful in case of program freezing
//	sigset_t normal; 

	const long WATCHDOG_KEEPALIVE_MS = 3000;
	const char *const WATCHPUPPY_SIGNALING_CMD = "killall -SIGUSR1 watchpuppy.sh";
	const long WATCHPUPPY_SIGNALING_CYCLE_S = 25; // needs to be smaller than TIMETOWAKEUP in watchpuppy.sh

	const double CYCLE_LEN_MULT = 1.0; // To globaly manipulate cycle_len

	// Constants for flight phase evaluation by height differences calculated by air pressure

	// Rough false value filtering
	const double HEIGHT_MIN = -500.0; // coast of dead sea
	const double HEIGHT_MAX = 50000.0; // balloon ascension limit + some puffer due to height calculation formular error in higher heights

	const unsigned char HEIGHT_AMT = 10; // Amount to collect, before calculate average vertical speed, false values included
	const double V_VELO_HALT_TO_FLIGHT_THRESHOLD = 0.5; // Threshold in m/s
	const uint16_t V_MOVEMENT_FILTER_MSK = 0xFFFF; // Bitmask to get last 16 Bits
	const uint16_t V_MOVEMENT_LANDED_MSK = 0x0000; // Bitmask to check for enough detected sequential non-movements
	const uint16_t  V_MOVEMENT_FLIGHING_MSK = V_MOVEMENT_FILTER_MSK; // Bitmask to check for enough detected sequential movements

	double height_last = 0.0; // Last height value
	double height_cur = 0.0; // Current height value
	double height_cnt = 0.0; // Counter for all height values, also false values
	double dheight_sum = 0.0; // Sum of delta height values

	double v_velo = 0.0; // vertical velocity in m/s
	uint16_t v_movement_seq = 0; // Bit counter for sequential vertical movement

	const long MASTER_LED_MS = 500;
	const long SENSORS_LED_MS = 100;
	int master_LED_on = 0;
	int sensors_LED_on = 0;


	// Camera configurations
	const camera_mode USB_CAMERA_MODE = PHOTO;
	const char *USB_CAMERA_FILENAME = "imgs/camera_usb_bottom";
	const camera_parameters USB_CAMERA_PARM = {
		0,
		10,    // freq, 1 picture per x seconds
//		1920, // width, too much CPU usage
//		1080, // height
//		1280, // width
//		720, // height
		640, // width
		480, // height
		0
	};
	char usb_camera_is_active = 0;

	const camera_mode RASPICAM_MODE = VIDEO;
	const char *RASPICAM_FILENAME = "vids/raspicam_side";
	const camera_parameters RASPICAM_PARM = {
		3 * 60 * 60,	  // duration, 3 hours, should be larger than flight time
		0,
		1920, // width
		1080, // height
		30    // framerate
	};
	char raspicam_is_active = 0;

	const long GSM_SEND_MS = 180000;  // Every 3 minutes.

	int tmp = 0;  // For unrelated use.

	// FLOW #1
	// Check for root privileges (bcm2835 doesn't do that automatically).
	if ((uid = getuid()) && geteuid() == uid)
	{
		errno = EPERM;
		fprintf(stderr, "%s:%i: root check failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR;
	}

	// FLOW #2.1
	// Setup signal handling
	// Maintenance handler
	maintenance_action.sa_flags = 0;
	sigemptyset(&maintenance_action.sa_mask);
	sigaddset(&maintenance_action.sa_mask, MAINTENANCE_SIGNAL);
	maintenance_action.sa_handler = sh_maintenance;
	tmp = sigaction(MAINTENANCE_SIGNAL, &maintenance_action, &maintenance_action_default);
	if (0 > tmp) {
		fprintf(stderr, "%s:%d: setting maintenance handler failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR;
	}
	// FLOW #2.2
	// Phase update handler
	update_action.sa_flags = 0;
	sigemptyset(&update_action.sa_mask);
	sigaddset(&update_action.sa_mask, UPDATE_SIGNAL);
	update_action.sa_handler = sh_update_phase;
	tmp = sigaction(UPDATE_SIGNAL, &update_action, &update_action_default);
	if (0 > tmp) {
		fprintf(stderr, "%s:%d: setting phase update handler failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_SIG_MAINT;
	}
	// Mask to block all signals
	tmp = sigfillset(&all);
	if (-1 == tmp) {
		fprintf(stderr, "%s:%i: filling blocking signal mask failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_SIG_UPDT;
	}

	// FLOW #3
	// Try reading a previously saved phase or instead use and write START.
	tmp = read_phase(&active_phase);
	if (EXIT_FAILURE == tmp) {
		active_phase = START;
		tmp = write_phase(active_phase);
		if (EXIT_FAILURE == tmp) {
			fprintf(stderr, "%s:%i: writing initial phase failed\n", __FILE__, __LINE__);
			rv = EXIT_FAILURE;
			goto ERR_SIG_UPDT;
		}
	}
	// FLOW #3.1
	// Update v_movement_seq
	if (FLIGHT == active_phase) {
		v_movement_seq = 0xFF;
	}

	// FLOW #4.1
	// bcm2835_init() comes with error message printing to stderr.
	if (!bcm2835_init()) {
		fprintf(stderr, "%s:%i: bcm2835_init failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_SIG_UPDT;
	}

	// FLOW #4.2
	// Initialize I2C functionality.
	if (!bcm2835_i2c_begin()) {
		fprintf(stderr, "%s:%i: I2C initialization failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_BCM_LIB_CLOSE;
	}

	// FLOW #5
	// Create and initialize array for connected sensors
	bcm2835_i2c_set_baudrate(MIN_BAUDRATE);
	if (EXIT_FAILURE == sensor_create_init_connected_sensors(SENSORS, SENSOR_AMT, &s_con, &s_con_amt)) {
		fprintf(stderr, "%s:%i: sensor_create_connected_sensors failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_BCM_I2C_END;
	}

	// FLOW #6
	// Determine greatest common I2C-frequency
	for (size_t i = 0; s_con_amt > i; i++) {
		if (NULL != s_con[i].reg) {
			if (baudrate > s_con[i].reg->i2c_freq_max) {
				baudrate = MIN_BAUDRATE;
			}
		}
	}
	bcm2835_i2c_set_baudrate(baudrate);

	// FLOW #7.1
	sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
	if (NULL == sensor_height) {
		fprintf(stderr, "%s:%d: no height sensor found. terminate program.\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_S_CON_DESTROY;
	}

	// FLOW #7.2
	sensor_get_best_by_date_type(s_con, s_con_amt, POS_LAT, &sensor_gps);
	if (NULL == sensor_gps) {
		fprintf(stderr, "%s:%d: no GPS sensor found. Terminate program.\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_S_CON_DESTROY;
	}

	// FLOW #8.1
	// Find length of longest sensor name to allocate sufficient memory for file names.
	if (INT_MAX < FORMAT_FILE_ID_LENGTH) {
		fprintf(stderr, "%s:%i: FORMAT_FILE_ID_LENGTH too large: expected 0..%i, got %zu\n", __FILE__, __LINE__, INT_MAX, FORMAT_FILE_ID_LENGTH);
		rv = EXIT_FAILURE;
		goto ERR_S_CON_DESTROY;
	}

	for (size_t i = 0; s_con_amt > i; ++i) {
		sensor_name_length = strlen(s_con[i].reg->name);
		if (sensor_name_length > file_name_length) {
			file_name_length = sensor_name_length;
		}
	}
	file_name_length += FORMAT_FILE_ID_LENGTH + 6;  // Length of "_###.csv\0".
	file_name = calloc(file_name_length, sizeof (char));
	if (NULL == file_name) {
		fprintf(stderr, "%s:%i: calloc of %zu bytes failed\n", __FILE__, __LINE__, file_name_length * sizeof (char));
		rv = EXIT_FAILURE;
		goto ERR_S_CON_DESTROY;
	}

	// FLOW #8.2
	// CSV-Preperations
	for (size_t i = 0; s_con_amt > i; ++i) {
		// Prepare filenames.
		tmp = snprintf(file_name, file_name_length, "%s_%0*zu.csv", s_con[i].reg->name, (int) FORMAT_FILE_ID_LENGTH, i);
		if (0 > tmp) {
			fprintf(stderr, "%s:%i: buffering file name for %s failed\n", __FILE__, __LINE__, s_con[i].reg->name);
			rv = EXIT_FAILURE;
			goto ERR_FILE_NAME_FREE;
		}

		// Open files.
		tmp = access(file_name, F_OK);  // Check if file exists (for CSV header).
		s_con[i].csv_file = open(file_name, O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
		if (0 > s_con[i].csv_file) {
			fprintf(stderr, "%s:%i: opening file %s failed: %s\n", __FILE__, __LINE__, file_name, strerror(errno));
			rv = EXIT_FAILURE;
			goto ERR_FILE_NAME_FREE;
		}
		// If file did not exist, write header.
		if (tmp) {
			errno = 0;
			written = write(s_con[i].csv_file, s_con[i].reg->csv_header, strlen(s_con[i].reg->csv_header));
			if (0 > written) {
				fprintf(stderr, "%s:%d: writing CSV header failed: %s\n", __FILE__, __LINE__, strerror(errno));
				rv = EXIT_FAILURE;
				goto ERR_FILE_NAME_FREE;
			}
		}
	}

	// FLOW #9.1
	// Create tasks
	tasks_amt = s_con_amt + TASK_TYPE_AMT - 1;
	tasks = malloc(tasks_amt * sizeof (struct task));
	if (NULL == tasks) {
		fprintf(stderr, "%s:%i: malloc of %zu bytes failed\n", __FILE__, __LINE__, file_name_length * sizeof (char));
		rv = EXIT_FAILURE;
		goto ERR_FILE_NAME_FREE;
	}
	// FLOW #9.2
	// Init tasks
	// Sensors
	for (size_t i = 0; s_con_amt > i; i++) {
		tasks[i].type = SENSOR;
		tasks[i].state = RUNNING;
		tasks[i].next_time = (struct timespec) {0, 0};
		tasks[i].p = &(s_con[i]);
		sensor = (i2c_sensor *) (tasks[i].p);
		tasks[i].active_phases = sensor->reg->active_phases;
		s_con[i].t = &(tasks[i]);

	}
	// LED tasks
	tasks[s_con_amt].type = LED_MASTER;
	tasks[s_con_amt].state = RUNNING;
	tasks[s_con_amt].active_phases = START | ASCEND;
	errno = 0;
	if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt].next_time))) {
		fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR_TASKS_FREE;
	}
	tasks[s_con_amt + 1].type = LED_SENSORS;
	tasks[s_con_amt + 1].state = RUNNING;
	tasks[s_con_amt + 1].active_phases = START | ASCEND;
	errno = 0;
	if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 1].next_time))) {
		fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR_TASKS_FREE;
	}
	// Fallback task for state change, if sensors or state transitions fail
	tasks[s_con_amt + 2].type = FALLBACK_STATE_TO_RECOVERY_CHANGER;
	tasks[s_con_amt + 2].state = RUNNING;
	tasks[s_con_amt + 2].active_phases = START | FLIGHT | RECOVERY;
	tasks[s_con_amt + 2].next_time = (struct timespec) {0, 0};
	tasks[s_con_amt + 2].p = NULL;
	// Camera USB task
	tasks[s_con_amt + 3].type = CAM_USB_TASK;
	tasks[s_con_amt + 3].state = RUNNING;
	tasks[s_con_amt + 3].active_phases = START | FLIGHT | RECOVERY;
	tasks[s_con_amt + 3].next_time = (struct timespec) {0, 0};
	// Camera raspi task
	tasks[s_con_amt + 4].type = CAM_RASPI_TASK;
	tasks[s_con_amt + 4].state = RUNNING;
	tasks[s_con_amt + 4].active_phases = START | FLIGHT | RECOVERY;
	tasks[s_con_amt + 4].next_time = (struct timespec) {0, 0};
	// GSM sender task
	tasks[s_con_amt + 5].type = GSM_TASK;
	tasks[s_con_amt + 5].state = RUNNING;
	tasks[s_con_amt + 5].active_phases = START | FLIGHT | RECOVERY;
	errno = 0;
	if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 5].next_time))) {
		fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR_TASKS_FREE;
	}
	// Watchdog task
	tasks[s_con_amt + 6].type = WATCHDOG;
	tasks[s_con_amt + 6].state = RUNNING;
	tasks[s_con_amt + 6].active_phases = START | FLIGHT | RECOVERY;
	errno = 0;
	if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 6].next_time))) {
		fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR_TASKS_FREE;
	}
	// Watchpuppy task
	tasks[s_con_amt + 7].type = WATCHPUPPY;
	tasks[s_con_amt + 7].state = RUNNING;
	tasks[s_con_amt + 7].active_phases = START | FLIGHT | RECOVERY;
	errno = 0;
	if (0 > clock_gettime(CLOCK_MONOTONIC, &(tasks[s_con_amt + 7].next_time))) {
		fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR_TASKS_FREE;
	}

	// FLOW #10
	// Update states by phases
	if (EXIT_FAILURE == task_update_states_by_phase(tasks, tasks_amt, active_phase)) {
		fprintf(stderr, "%s:%i: task_update_states_by_phase failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR_TASKS_FREE;
	}

	// Main loop
	while (QUIT != active_phase && EXIT_SUCCESS == task_get_next(tasks, tasks_amt, &task)) {

		// Is task ready? If not, sleep remaining time
		if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
			fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
			rv = EXIT_FAILURE;
			goto ERR_TASKS_FREE;
		}
		if (timespec_gt(task->next_time, cur_time)) {
			diff_time = timespec_sub(task->next_time, cur_time);
			do {
				errno = 0;
			} while (0 > clock_nanosleep(CLOCK_MONOTONIC, 0,&diff_time, &diff_time) && EINTR == errno);
		}

		switch (task->type) {
			case SENSOR:

			sensor = (i2c_sensor*) (task->p);
				// Sensor measurement cycle
				// Repeat, as long as sensor isn't sleeping and there's no delay
				do {

					// TODO-DEL - Signal-Blocking while sensors running not useful in case of program freezing
//					// Block all signals.
//					tmp = sigprocmask(SIG_BLOCK, &all, &normal);
//					if (-1 == tmp) {
//						fprintf(stderr, "%s:%i: setting blocking signal mask failed\n", __FILE__, __LINE__);
//					}

					switch (sensor->state) {

						case INIT:
							if (EXIT_FAILURE == sensor->reg->init(active_phase, &(sensor->it_rem), &(sensor->processing_time))) { // Init_Err
								fprintf(stderr, "%s:%i: init for %s failed\n", __FILE__, __LINE__, sensor->reg->name);
								sensor->err_seq++;
								sensor->err_total++;
								if (ERR_THRESHOLD <= sensor->err_seq) {
									sensor->state = FAILURE;
									sensor->failure_total++;
									if (sensor == sensor_height) {
										sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
									}
									sensor->processing_time = ERR_PERIOD_MS;
								} else {
									sensor->processing_time = ERR_SEQ_DELAY_MS;
								}
							} else { // Init_Succ
								sensor->err_seq = 0;
								if (0 < sensor->processing_time) {
									// Update next_time
									if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
										fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
										rv = EXIT_FAILURE;
										goto ERR_TASKS_FREE;
									}
									task->next_time = timespec_add_ms(cur_time, sensor->processing_time);
								}
								if (0 < sensor->it_rem) { // Init_Unfinished
									sensor->it_rem--;
								} else { // Init_Finished
									sensor->state = MEASURE;
									sensor->it_rem = -1;
									// Set sensor->next_cycle_time
									if (0 > clock_gettime(CLOCK_MONOTONIC, &(sensor->next_cycle_time))) {
										fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
										rv = EXIT_FAILURE;
										goto ERR_TASKS_FREE;
									}
									sensor->next_cycle_time = timespec_add_ms(sensor->next_cycle_time, sensor->reg->cycle_len * CYCLE_LEN_MULT);
								}
							}
							break;
						case MEASURE:
							if (EXIT_FAILURE == sensor->reg->measure(active_phase, &(sensor->measurements), &(sensor->it_rem), &(sensor->processing_time))) { // Measure_Err
								fprintf(stderr, "%s:%i: measure for %s failed\n", __FILE__, __LINE__, sensor->reg->name);
								sensor->err_seq++;
								sensor->err_total++;
								if (ERR_THRESHOLD <= sensor->err_seq) {
									sensor->state = FAILURE;
									sensor->failure_total++;
									if (sensor == sensor_height) {
										sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
									}
									sensor->processing_time = 0;
								} else {
									sensor->processing_time = ERR_SEQ_DELAY_MS;
								}
							} else { // Measure_Succ
								sensor->err_seq = 0;
								if (0 < sensor->it_rem) { // Measure_Unfinished
									sensor->it_rem--;
									if (0 < sensor->processing_time) {
										// Update next_time
										if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
											fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
											rv = EXIT_FAILURE;
											goto ERR_TASKS_FREE;
										}
										task->next_time = timespec_add_ms(cur_time, sensor->processing_time);
									}
								} else { // Measure_Finished
									sensor->it_rem = -1;
									// TODO-UC
//									task->next_time = timespec_add_ms(sensor->next_cycle_time, sensor->processing_time);
//									sensor->next_cycle_time = timespec_add_ms(sensor->next_cycle_time, sensor->reg->cycle_len * CYCLE_LEN_MULT);

									// TODO-TEST-2-B
									if (0 > clock_gettime(CLOCK_MONOTONIC, &cur_time)) {
										fprintf(stderr, "%s:%i: acquiring time failed: %s\n", __FILE__, __LINE__, strerror(errno));
										rv = EXIT_FAILURE;
										goto ERR_TASKS_FREE;
									}
									task->next_time = timespec_add_ms(cur_time, sensor->processing_time);
									sensor->next_cycle_time = timespec_add_ms(sensor->next_cycle_time, sensor->reg->cycle_len * CYCLE_LEN_MULT);
									if (timespec_gt(sensor->next_cycle_time, task->next_time)) {
										task->next_time = sensor->next_cycle_time;
									} else {
										fprintf(stderr, "%s:%i: %ld.%03ld: sensor %s cycle_len exceeded\n", __FILE__, __LINE__, cur_time.tv_sec, cur_time.tv_nsec / 1000000, sensor->reg->name);
										sensor->next_cycle_time = task->next_time;
									}
									// TODO-TEST-2-E

									sensor->processing_time = 1; // Actual value doesn't matter, just to stop sensor loop
									if (EXIT_FAILURE == measurement_append_to_csv_file(sensor->csv_file, sensor->measurements)) {
										fprintf(stderr, "%s:%i: append_to_csv_file for %s's data failed\n", __FILE__, __LINE__, sensor->reg->name);
									}
									// Test for phase switching conditions.
									if (sensor_height == sensor) { // New height value?
										// TODO-MV - move to sensor_height probing an save in pointer
										for (size_t i = 0; sensor->reg->date_amt > i; i++) { // get height
											if (HEIGHT == sensor->reg->types[i]) {
												height_cur = sensor->measurements.data[i];
												break;
											}
										}
										if (HEIGHT_MIN < height_cur || HEIGHT_MAX > height_cur) { // No false value?
											dheight_sum += height_cur - height_last;
											height_last = height_cur;
										}
										height_cnt++;
										if (HEIGHT_AMT == height_cnt) { // Enough values collected? Calculate vert. velocity
											v_velo = dheight_sum / height_cnt / (double) sensor->reg->cycle_len * 1000;
											height_cnt = 0.0;
											dheight_sum = 0.0;

											if (v_velo > V_VELO_HALT_TO_FLIGHT_THRESHOLD || v_velo < (-1 * V_VELO_HALT_TO_FLIGHT_THRESHOLD)) { // Threshold to detect movement exceeded?
												v_movement_seq = v_movement_seq << 1 | 0x01;
												if (START == active_phase && V_MOVEMENT_FLIGHING_MSK == (v_movement_seq & V_MOVEMENT_FILTER_MSK)) { // In START pahse and enough movements detected?
													printf("Phase switched to FLIGHT!\n");
													active_phase = FLIGHT;
													tmp = write_phase(active_phase);
													if (EXIT_FAILURE == tmp) {
														fprintf(stderr, "%s:%i: writing updated phase failed: active_phase=%u\n", __FILE__, __LINE__, active_phase);
													}
													// Update states by phases
													if (EXIT_FAILURE == task_update_states_by_phase(tasks, tasks_amt, active_phase)) {
														fprintf(stderr, "%s:%i: task_update_states_by_phase failed\n", __FILE__, __LINE__);
														rv = EXIT_FAILURE;
														goto ERR_TASKS_FREE;
													}
												}
											} else { // Thresehold not exceeded
												v_movement_seq = (v_movement_seq << 1) | 0x0000;
												if (FLIGHT == active_phase && V_MOVEMENT_LANDED_MSK == (v_movement_seq & V_MOVEMENT_FILTER_MSK)) { // Enough stationaries detected?
													printf("Phase switched to RECOVERY!\n");
													active_phase = RECOVERY;
													tmp = write_phase(active_phase);
													if (EXIT_FAILURE == tmp) {
														fprintf(stderr, "%s:%i: writing updated phase failed: active_phase=%u\n", __FILE__, __LINE__, active_phase);
													}
													// Update states by phases
													if (EXIT_FAILURE == task_update_states_by_phase(tasks, tasks_amt, active_phase)) {
														fprintf(stderr, "%s:%i: task_update_states_by_phase failed\n", __FILE__, __LINE__);
														rv = EXIT_FAILURE;
														goto ERR_TASKS_FREE;
													}
												}
											}
//											printf("s_name = %s\nheight_cur = %lf\nv_velo = %lf\nv_movement_seq = 0x%04x\n\n", sensor->reg->name, height_cur, v_velo, v_movement_seq); // TODO-DEL
										}
									}
								}
							}
							break;
						case FAILURE:
							if (EXIT_FAILURE == sensor->reg->is_connected()) { // Sensor still dead
								fprintf(stderr, "%s:%i: reconnect for %s failed\n", __FILE__, __LINE__, sensor->reg->name);
								sensor->err_total++;
								sensor->processing_time = ERR_PERIOD_MS;
								task->next_time = timespec_add_ms(task->next_time, sensor->processing_time);
							} else { // Sensor recovered
								fprintf(stderr, "%s:%i: reconnect for %s successful\n", __FILE__, __LINE__, sensor->reg->name);
								sensor->err_seq = 0;
								sensor->state = INIT;
								sensor->it_rem = -1;
								if (EXIT_SUCCESS == sensor_has_date_type(*sensor, HEIGHT)) {
									sensor_get_best_by_date_type(s_con, s_con_amt, HEIGHT, &sensor_height);
								}
								sensor->processing_time = 0;
							}
							break;
						default:
							fprintf(stderr, "%s:%i: sensor \"%s\" has illegal state: %d. Stopped sensor task\n", __FILE__, __LINE__, sensor->reg->name, sensor->state);
							task->state = STOPPED;
					}
					// TODO-DEL - Signal-Blocking while sensors running not useful in case of program freezing
//					// Reset signal mask.
//					tmp = sigprocmask(SIG_SETMASK, &normal, NULL);
//					if (-1 == tmp) {
//						fprintf(stderr, "%s:%i: returning to normal signal mask failed\n", __FILE__, __LINE__);
//					}
				} while (SLEEP != sensor->state && 0 >= sensor->processing_time);
				break;

			case LED_MASTER:
				if (START == active_phase) {
					// Blinks if master is running.
					master_LED_on = !master_LED_on;
					set_taskLED(task->type, master_LED_on);

					task->next_time = timespec_add_ms(task->next_time, MASTER_LED_MS);
				} else {
					set_taskLED(task->type, 0);
					task->state = STOPPED;
				}
				break;

			case LED_SENSORS:
				if (START == active_phase) {
					// Count current failures.
					tmp = 0;
					for (size_t i = 0; i < s_con_amt; ++i) {
						if (FAILURE == s_con[i].state) {
							++tmp;
						}
					}

					// Disconnected sensors or current failures -> off.
					if (SENSOR_AMT != s_con_amt || tmp) {
						sensors_LED_on = 0;
						// Any past failures -> blinking; no failures at all -> on.
					} else {
						tmp = 0;
						for (size_t i = 0; i < s_con_amt; ++i) {
							tmp += s_con[i].failure_total;
						}
						sensors_LED_on = tmp ? !sensors_LED_on : 1;
					}
					set_taskLED(task->type, sensors_LED_on);

					task->next_time = timespec_add_ms(task->next_time, SENSORS_LED_MS);
				} else {
					set_taskLED(task->type, 0);
					task->state = STOPPED;
				}
				break;

			case CAM_RASPI_TASK:
				switch (active_phase) {
					case RECOVERY:
						if (raspicam_is_active) {
							CAMERAS[0].stop();
							raspicam_is_active = 0;
						}
						break;
					default: // START, FLIGHT, etc
						if (!raspicam_is_active) {
							CAMERAS[0].start(RASPICAM_MODE, RASPICAM_FILENAME, RASPICAM_PARM);
							raspicam_is_active = 1;
						}
				}
				task->state = STOPPED;
				break;

			case CAM_USB_TASK:
				switch (active_phase) {
					case RECOVERY:
						if (raspicam_is_active) {
							CAMERAS[1].stop();
							usb_camera_is_active = 0;
						}
						break;
					default: // START, FLIGHT, etc
						if (!usb_camera_is_active) {
							CAMERAS[1].start(USB_CAMERA_MODE, USB_CAMERA_FILENAME, USB_CAMERA_PARM);
							usb_camera_is_active = 1;
						}
				}
				task->state = STOPPED;
				break;

			case GSM_TASK:
//				send_GSM(&sensor_gps->measurements); // TODO-UC
				task->next_time = timespec_add_ms(task->next_time, GSM_SEND_MS);
				break;

			case FALLBACK_STATE_TO_RECOVERY_CHANGER:
				if (EXIT_FAILURE == fallback_recovery_task(task, &active_phase, cur_time)) {
					fprintf(stderr, "%s:%s:%i: fallback_recovery_task failed\n", __FILE__, __func__, __LINE__);
					rv = EXIT_FAILURE;
					goto ERR_TASKS_FREE;
				}
				break;

			case WATCHDOG:
				wake_watchdog();
				task->next_time = timespec_add_ms(task->next_time, WATCHDOG_KEEPALIVE_MS);
				break;

			case WATCHPUPPY:
				system(WATCHPUPPY_SIGNALING_CMD);
				task->next_time = timespec_add_ms(task->next_time, WATCHPUPPY_SIGNALING_CYCLE_S * 1000);
				break;

			default:
				fprintf(stderr, "%s:%i: task with faulty task_type\n", __FILE__, __LINE__);
		}
	}

	// Cleanup in reverse order.
ERR_TASKS_FREE:
	free(tasks);
ERR_FILE_NAME_FREE:
	free(file_name);
ERR_S_CON_DESTROY:
	for (size_t i = 0; s_con_amt > i; i++) {
		measurement_destroy(&(s_con[i].measurements));
	}
	free(s_con);
ERR_BCM_I2C_END:
	bcm2835_i2c_end();
ERR_BCM_LIB_CLOSE:
	if (!bcm2835_close()) {
		rv = EXIT_FAILURE;
	}
ERR_SIG_UPDT:
	tmp = sigaction(UPDATE_SIGNAL, &update_action_default, NULL);
	if (0 > tmp) {
		fprintf(stderr, "%s:%d: resetting phase update handler to default failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
	}
ERR_SIG_MAINT:
	tmp = sigaction(MAINTENANCE_SIGNAL, &maintenance_action_default, NULL);
	if (0 > tmp) {
		fprintf(stderr, "%s:%d: resetting maintenance handler to default failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
	}
ERR:
	return rv;
}


int read_phase(phase *const p) {
	FILE *file = NULL;
	int tmp = 0;
	int rv = EXIT_SUCCESS;

	if (NULL == p) {
		fprintf(stderr, "%s:%i: no phase given\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	/* Automated creation not specified in header.
	// File doesn't exist.
	tmp = access(file_name, F_OK);
	if (tmp) {
	 *p = START;
	 return write_phase(*p);
	 }
	 */

	file = fopen(PERSISTENCE_PATH, "r");
	if (NULL == file) {
		fprintf(stderr, "%s:%i: opening persistence file for reading failed: %s\n", __FILE__, __LINE__, strerror(errno));
		return EXIT_FAILURE;
	}

	errno = 0;
	tmp = fscanf(file, "%u", p);
	if (1 != tmp) {
		fprintf(stderr, "%s:%i: scanning for phase failed: %s\n", __FILE__, __LINE__, 0 == errno ? "no value" : strerror(errno));
		rv = EXIT_FAILURE;
	}

	tmp = fclose(file);
	if (tmp) {
		fprintf(stderr, "%s:%i: closing persistence file after reading failed: %s\n", __FILE__, __LINE__, strerror(errno));
		return EXIT_FAILURE;
	}

	return rv;
}


int write_phase(phase p) {
	FILE *file = NULL;
	int tmp = 0;

	file = fopen(PERSISTENCE_PATH, "w");
	if (NULL == file) {
		fprintf(stderr, "%s:%i: opening persistence file for writing failed: %s\n", __FILE__, __LINE__, strerror(errno));
		return EXIT_FAILURE;
	}

	tmp = fprintf(file, "%i", p);
	if (0 >= tmp) {
		fprintf(stderr, "%s:%i: printing phase failed\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	tmp = fclose(file);
	if (tmp) {
		fprintf(stderr, "%s:%i: closing persistence file after writing failed: %s\n", __FILE__, __LINE__, strerror(errno));
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}



void sh_maintenance(int sig) {
	unsigned long total_failures = 0;
	size_t in_failure = 0;

	size_t i_all = 0;

	if (MAINTENANCE_SIGNAL == sig) {
		// Collect combined metrics.
		for (size_t i = 0; i < s_con_amt; ++i) {
			total_failures += s_con[i].failure_total;
			if (FAILURE == s_con[i].state) {
				++in_failure;
			}
		}

		// TODO-IMPL - Show master status as following:
		// Master-Status CRITICAL, all height sensors in FAILURE, or no height sensor
		printf("\n[%s][%s] %-10s (", 0 == total_failures ? "\033[0;92m GOOD \033[0m" : (0 == in_failure ? "\033[0;93m OKAY \033[0m" : "\033[0;33m ALRM \033[0m"), 0 < s_con_amt ? "\033[0;36mACTIVE\033[0m" : "\033[0;30m DEAD \033[0m", "master");
		switch (active_phase) {
			// Single phases.
			case START:
				printf("START");
				break;
			case ASCEND:
				printf("ASCEND");
				break;
			case DESCEND:
				printf("DESCEND");
				break;
			case RECOVERY:
				printf("RECOVERY");
				break;
				// Combined phases (name strings ending with '*').
			case FLIGHT:
				printf("FLIGHT*");
				break;
			default:
				printf("\033[0;90m?active_phase=%u?\033[0m", active_phase);
		}
		printf(" phase, %zu/%zu sensors connected, %lu failures total, %zu sensors in FAILURE)\n\n", s_con_amt, SENSOR_AMT, total_failures, in_failure);

		printf("[ COND ][  TASK   ][STATE ]    NAME    (ERROR STATISTICS)\n");
		for (size_t i = 0; i < s_con_amt; ++i) {
			printf("[%s][%s][%s] %-10s (%lu failures total, %lu errors total, %u erorrs in sequence)\n",
					0 == s_con[i].err_total ? "\033[0;92m GOOD \033[0m" : (FAILURE == s_con[i].state ? "\033[0;91m FAIL \033[0m" : (0 == s_con[i].err_seq ? "\033[0;33m OKAY \033[0m" : "\033[0;33mINTRUP\033[0m")),
					RUNNING == s_con[i].t->state ? "\033[0;92m RUNNING \033[0m" : "\033[0;34m STOPPED \033[0m",
					INIT == s_con[i].state ? "\033[0;95m INIT \033[0m" : (!s_con[i].reg->active_phases ? "\033[0;35mHYBERN\033[0m" : (s_con[i].reg->active_phases & active_phase ? "\033[0;36mACTIVE\033[0m" : "\033[0;34mASLEEP\033[0m")),
					s_con[i].reg->name, s_con[i].failure_total, s_con[i].err_total, s_con[i].err_seq);
		}
		if (SENSOR_AMT > s_con_amt) {
			for (size_t i_con = 0; SENSOR_AMT > i_all && i_con < s_con_amt; ++i_all) {
				if (&SENSORS[i_all] == s_con[i_con].reg) {
					++i_con;
				} else {
					printf("[\033[0;30mDISCON\033[0m][      ] %-10s\n", SENSORS[i_all].name);
				}
			}
			for (; SENSOR_AMT > i_all; ++i_all) {
				printf("[\033[0;30mDISCON\033[0m][      ] %-10s\n", SENSORS[i_all].name);
			}
		}

		fflush(stdout);
	}
}

void sh_update_phase(int sig) {
	sigset_t all;
	sigset_t normal;
	int tmp = 0;

	tmp = sigfillset(&all);
	if (-1 == tmp) {
		fprintf(stderr, "%s:%i: filling blocking signal mask failed\n", __FILE__, __LINE__);
	}
	tmp = sigprocmask(SIG_BLOCK, &all, &normal);
	if (-1 == tmp) {
		fprintf(stderr, "%s:%i: setting blocking signal mask failed\n", __FILE__, __LINE__);
	}

	if (UPDATE_SIGNAL == sig) {
		tmp = read_phase(&active_phase);
		if (EXIT_FAILURE == tmp) {
			fprintf(stderr, "%s:%i: reading phase on signal failed\n", __FILE__, __LINE__);
		}
		tmp = task_update_states_by_phase(tasks, tasks_amt, active_phase);
		if (EXIT_FAILURE == tmp) {
			fprintf(stderr, "%s:%s:%i: task_update_states_by_phase failed\n", __FILE__, __func__, __LINE__);
		}
	}

	tmp = sigprocmask(SIG_SETMASK, &normal, NULL);
	if (-1 == tmp) {
		fprintf(stderr, "%s:%i: returning to normal signal mask failed\n", __FILE__, __LINE__);
	}
}


int fallback_recovery_task(task *thiz, phase *cur_phase, struct timespec cur_time) {
	FILE *file = NULL;
	int rv = EXIT_SUCCESS;
	int rv_f = 0;

	if (NULL == thiz) {
		fprintf(stderr, "%s:%s:%i: Input thiz is NULL\n", __FILE__, __func__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == cur_phase) {
		fprintf(stderr, "%s:%s:%i: Input cur_phase is NULL\n", __FILE__, __func__, __LINE__);
		return EXIT_FAILURE;
	}

	switch (*cur_phase) {
		case START: // Set next_time and save it
			// Set next_time
			thiz->next_time.tv_sec = cur_time.tv_sec + MAX_FLIGHT_TIME_S;
			thiz->next_time.tv_nsec = cur_time.tv_nsec;
			// Write next_time to file
			file = fopen(MAX_FLIGHT_TIME_PATH, "w");
			if (NULL == file) {
				fprintf(stderr, "%s:%s:%i: fopen failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
				return EXIT_FAILURE;
			}
			rv_f = fprintf(file, "%ld\n%ld", thiz->next_time.tv_sec, thiz->next_time.tv_nsec);
			if (0 >= rv_f) {
				fprintf(stderr, "%s:%s:%i: fprintf failed and returned %d\n", __FILE__, __func__, __LINE__, rv_f);
				rv = EXIT_FAILURE;
			}
			rv_f = fclose(file);
			if (EOF == rv_f) {
				fprintf(stderr, "%s:%s:%i: fclose failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
				return EXIT_FAILURE;
			}
			break;

		case RECOVERY: // Stopp own task
			thiz->state = STOPPED;
		break;

		default: // Check for passed MAX_FLIGHT_TIME
			// Is next_time not set?
			if (0 == thiz->next_time.tv_sec && 0 == thiz->next_time.tv_nsec) {
				// Read next_time from file
				file = fopen(MAX_FLIGHT_TIME_PATH, "r");
				if (NULL == file) {
					fprintf(stderr, "%s:%s:%i: fopen failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
					return EXIT_FAILURE;
				}
				rv_f = fscanf(file, "%ld\n%ld", &(thiz->next_time.tv_sec), &(thiz->next_time.tv_nsec));
				errno = 0;
				if (2 != rv_f) {
					fprintf(stderr, "%s:%i: fscanf failed: %s\n", __FILE__, __LINE__, 0 == errno ? "faulty file content" : strerror(errno));
					rv = EXIT_FAILURE;
				}
				rv_f = fclose(file);
				if (EOF == rv_f) {
					fprintf(stderr, "%s:%s:%i: fclose failed: %s\n", __FILE__, __func__, __LINE__, strerror(errno));
					return EXIT_FAILURE;
				}
			}
			// Is MAX_FLIGHT_TIME reached?
			if (timespec_lt(thiz->next_time, cur_time)) {
				fprintf(stderr, "%s:%s:%i: MAX_FLIGHT_TIME_S = %ld s reached. Switched state to RECOVERY.\n", __FILE__, __func__, __LINE__, MAX_FLIGHT_TIME_S);
				*cur_phase = RECOVERY;
				// Update phase
				rv_f = task_update_states_by_phase(tasks, tasks_amt, active_phase);
				if (EXIT_FAILURE == rv_f) {
					fprintf(stderr, "%s:%s:%i: task_update_states_by_phase failed\n", __FILE__, __func__, __LINE__);
					return EXIT_FAILURE;
				}
				rv_f = write_phase(*cur_phase);
				if (EXIT_FAILURE == rv_f) {
					fprintf(stderr, "%s:%s:%i: write_phase failed\n", __FILE__, __func__, __LINE__);
					return EXIT_FAILURE;
				}
				// Stopp own task
				thiz->state = STOPPED;
			}
	}
	
	return rv;
}
definitions.h
/**
 * @file  definitions.h
 * @brief Definitions used by multiple header files
 *
 * @author Florian Schwarz
 */

#ifndef INCLUDE_DEFINITIONS_H_
#define INCLUDE_DEFINITIONS_H_


/**
 * @brief Possible operating phases of weather balloon as bit mask in chronological
 *        order.
 *
 * Combine masks with bitwise OR ("|") and check for phase with bitwise AND
 * ("&"). The result of this is automatically usable as boolean value.\n
 * \n
 * Keeping a chronological order is important for being able to switch through
 * the phases by just bit-shifting the active phase.
 */
typedef enum phase {
	START    = 0x01,  ///< Pre-flight phase.
	ASCEND   = 0x02,  ///< Ascending phase.
	DESCEND  = 0x04,  ///< Descending phase.
	RECOVERY = 0x08,  ///< Recovery phase.

	// Special phases.
	QUIT     = 0x10,   ///< Not an actual phase. Triggers program termination.

	// Combined phases.
	FLIGHT   = 0x06    ///< ASCEND | DESCEND
} phase;

#endif  // INCLUDE_DEFINITIONS_H_
measurement.h
/**
 * @file  measurement.h
 * @brief Definitions for the Master programm.
 *
 * @author Florian Schwarz, Jonas Gaida
 */

#ifndef INCLUDE_MEASUREMENTDATA_H_
#define INCLUDE_MEASUREMENTDATA_H_

#include <stddef.h>  // size_t
#include <time.h>  // struct timespec



/**
 * @brief Possible types of data stored in a measurement struct data field.
 *
 * @sa measurement
 */
typedef enum date_type {
	EXOTIC,             ///< Something else.
	HEIGHT,             ///< Height in m (meter).
	PRESSURE,           ///< Atmospheric pressure in hPa (hectopascal).
	TEMPERATURE,        ///< Temperature in °C (degree Celsius).
	HUMIDITY,           ///< Relative humidity in % (percent).
	ACCELERATION_X,     ///< Acceleration in m/s^2 (meter per second squared).
	ACCELERATION_Y,     ///< Acceleration in m/s^2 (meter per second squared).
	ACCELERATION_Z,     ///< Acceleration in m/s^2 (meter per second squared).
	ROTATION_X,         ///< Rotation in °/s (degree per second).
	ROTATION_Y,         ///< Rotation in °/s (degree per second).
	ROTATION_Z,         ///< Rotation in °/s (degree per second).
	MAGNETIC_FLUX_X,    ///< Magnetic flux density in T (Tesla).
	MAGNETIC_FLUX_Y,    ///< Magnetic flux density in T (Tesla).
	MAGNETIC_FLUX_Z,    ///< Magnetic flux density in T (Tesla).
	IRRADIANCE,         ///< Irradiance of UV radiation in W/m^2 (Watt per square meter).
	LUM_INTENSITY,      ///< Intensity of visible light in cd (candela).
	POS_LAT,            ///< Latitude in DD (decimal degree).
	POS_LON             ///< Longitute in DD (decimal degree).
} date_type;

/**
 * @brief Interpreted measurements of a sensor of a specified kind.
 */
typedef struct measurement {
	struct timespec time;        ///< Unix time when the data was recorded.
	size_t          data_c;      ///< How many data fields exist.
	date_type       *types;      ///< Which kind of data is stored in each field.
	signed char     *priorities; ///< Priority for each field. May be used to select most accurat sensor for master, e.g. height determination. 0 default, the higher, the more prioritized
	double          *data;       ///< Array of data fields.
} measurement;



/**
 * @brief Creates data array by allocating uninitialized memory and sets
 *        attributes. Priorities are set to 0.
 *
 * If @p data_c is zero, the method returns successfully and @c types and
 * @c data are set to @c NULL. This is for other devices posing as sensors.
 *
 * @param[out] m      Pointer to an existing measurement struct.
 * @param[in]  data_c Number of data fields.
 * @param[in]  types  Types of data stored in @c m->data. Length is @p data_c.
 * @param[in]  priorities Priorities for each measurement @c m->data. Length is @p data_c. If Null, all priorities will get set to 0.
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 *
 * @sa measurement_destroy
 */
int measurement_create(measurement *const m, size_t data_c, const date_type *const types, const signed char *const priorities);

/**
 * @brief Destroys data array by freeing memory and resets all attributes.
 *
 * If @c m.data_c is zero and @c m.data is @c NULL, nothing happens and the
 * method returns successfully.
 *
 * @param[out] m Pointer to an existing measurement struct.
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 *
 * @sa measurement_create
 */
int measurement_destroy(measurement *const m);

/**
 * @brief Append a measurement as line in CSV format to an open, writable file.
 *
 * @param[in] fd   Open file descriptor with write permission.
 * @param[in] m The measurement struct to be written.
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int measurement_append_to_csv_file(int fd, measurement m);


/**
 * @brief Prints info about measurement struct
 *
 * @param[in] m measurement struct to print
 */
void measurement_print(measurement m);


#endif  // INCLUDE_MEASUREMENTDATA_H_
measurement.c
/**
 * @file  measurement.c
 * @brief Implementation of measurement.h
 *
 * @author Jonas Gaida, Florian Schwarz
 */

#include "measurement.h"

#include <stdlib.h>  // exit codes
#include <unistd.h> // write
#include <stdio.h> // fprintf
#include <string.h>  // strerror, strlen, memcpy
#include <fcntl.h>  // fcntl
#include <errno.h>  // errno



/*
 * Local definitions of CSV symbols.
 */
static const char TIME_SEP = '.';
static const char CSV_SEP = ';';
static const char CSV_NEWLINE = '\n';



/**
 * @brief Local helper function for converting a measurement to a CSV line.
 *
 * Does not validate arguments!
 *
 * @param[in] m The measurement to be converted.
 *
 * @return Newly allocated memory containing the line as null-terminated string.
 *
 * @sa measurement_append_csv_to_file
 */
static char *measurement_to_csv(measurement m) {
	int tmp_length = 0;

	size_t line_length = 1;  // Always has a null byte.
	size_t pos = 0;
	char *line = NULL;

	int status = EXIT_SUCCESS;



	// Determine the length of the time as string.
	tmp_length = snprintf(NULL, 0, "%li%c%09li", m.time.tv_sec, TIME_SEP, m.time.tv_nsec);
	if (0 > tmp_length) {
		fprintf(stderr, "%s:%i: determining time length failed\n", __FILE__, __LINE__);
		status = EXIT_FAILURE;
		goto ERR;
	}
	line_length += (size_t) tmp_length;

	for (size_t i = 0; m.data_c > i; ++i) {
		tmp_length = snprintf(NULL, 0, "%lf", m.data[i]);
		if (0 > tmp_length) {
			fprintf(stderr, "%s:%i: determining data length failed: index %zu\n", __FILE__, __LINE__, i);
			status = EXIT_FAILURE;
			goto ERR;
		}

		// Accumulate field lengths and add space for a CSV_SEP.
		line_length += 1 + (size_t) tmp_length;
	}

	// Add space for the final CSV_NEWLINE.
	++line_length;



	// Allocate memory for the line buffer.
	line = calloc(line_length, sizeof (char));
	if (NULL == line) {
		fprintf(stderr, "%s:%i: calloc of %zu bytes failed\n", __FILE__, __LINE__, line_length * sizeof (char));
		status = EXIT_FAILURE;
		goto ERR;
	}



	// Print time first.
	tmp_length = snprintf(line, line_length, "%li%c%09li", m.time.tv_sec, TIME_SEP, m.time.tv_nsec);
	if (0 > tmp_length) {
		fprintf(stderr, "%s:%i: buffering time failed\n", __FILE__, __LINE__);
		status = EXIT_FAILURE;
		goto ERR_FREE;
	}
	pos = (size_t) tmp_length;

	// Print each data point preceeded by a CSV_SEP.
	for (size_t i = 0; m.data_c > i; ++i) {
		tmp_length = snprintf(&line[pos], line_length - pos, "%c%f", CSV_SEP, m.data[i]);
		if (0 > tmp_length) {
			fprintf(stderr, "%s:%i: buffering data failed: index %zu\n", __FILE__, __LINE__, i);
			status = EXIT_FAILURE;
			goto ERR_FREE;
		}
		pos += (size_t) tmp_length;
	}

	// Print final CSV_NEWLINE and closing null byte.
	tmp_length = snprintf(&line[pos], line_length - pos, "%c", CSV_NEWLINE);
	if (0 > tmp_length) {
		fprintf(stderr, "%s:%i: buffering new line failed\n", __FILE__, __LINE__);
		status = EXIT_FAILURE;
		goto ERR_FREE;
	}

ERR_FREE:
	if (EXIT_FAILURE == status) {
		free(line);
		line = NULL;
	}

ERR:
	return line;
}


int measurement_create(measurement *const m, size_t data_c, const date_type *const types, const signed char *const priorities) {
	int rv = EXIT_SUCCESS;
	
	if (NULL == m) {
		fprintf(stderr, "%s:%i: no measurement given\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == types && data_c > 0) {
		fprintf(stderr, "%s:%i: Input types is Null\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == priorities && data_c > 0) {
		fprintf(stderr, "%s:%i: Input priorities is Null\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	m->types = calloc(data_c, sizeof (date_type));
	if (NULL == m->types && data_c > 0) {
		fprintf(stderr, "%s:%i: calloc of %zu bytes failed\n", __FILE__, __LINE__, data_c * sizeof (date_type));
		return EXIT_FAILURE;
	}
	m->priorities = calloc(data_c, sizeof (signed char));
	if (NULL == m->priorities && data_c > 0) {
		fprintf(stderr, "%s:%i: calloc of %zu bytes failed\n", __FILE__, __LINE__, data_c * sizeof (signed char));
		rv = EXIT_FAILURE;
		goto MEASURE_ERR_FREE_TYPES;
	}
	m->data = calloc(data_c, sizeof (double));
	if (NULL == m->data && data_c > 0) {
		fprintf(stderr, "%s:%i: calloc of %zu bytes failed\n", __FILE__, __LINE__, data_c * sizeof (double));
		rv = EXIT_FAILURE;
		goto MEASURE_ERR_FREE_PRIORITIES;
	}

	for (size_t i = 0; data_c > i; i++) {
		m->types[i] = types[i];
		m->priorities[i] = priorities[i];
	}


if (EXIT_FAILURE == rv) {
MEASURE_ERR_FREE_PRIORITIES:
	free(m->priorities);
MEASURE_ERR_FREE_TYPES:
	free(m->types);

}
m->data_c = data_c;

return rv;
}

int measurement_destroy(measurement *const m) {
	if (NULL == m) {
		fprintf(stderr, "%s:%i: Input m is Null\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	// Has no effect, if pointer is NULL.
	free(m->types);
	free(m->data);

	// Reset attributes to avoid undefined behavior on later use.
	m->data_c = 0;
	m->types = NULL;
	m->data = NULL;

	return EXIT_SUCCESS;
}

int measurement_append_to_csv_file(int fd, measurement m) {
	size_t line_length = 0;
	char *line = NULL;
	ssize_t written = 0;

	int rv = EXIT_SUCCESS;

	if (-1 == fcntl(fd, F_GETFD)) {
		fprintf(stderr, "%s:%i: invalid file handle given\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR;
	}

	if (0 == m.data_c || NULL == m.data) {
		fprintf(stderr, "%s:%i: uninitialized measurement given\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR;
	}



	// Generate CSV line.
	line = measurement_to_csv(m);
	if (NULL == line) {
		fprintf(stderr, "%s:%i: generating CSV line failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;
		goto ERR;
	}
	line_length = strlen(line);

	// Write line to file.
	written = write(fd, line, line_length);
	if (0 > written) {
		fprintf(stderr, "%s:%d: writing CSV line failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR_FREE;
	}
	if (line_length != (size_t) written) {
		fprintf(stderr, "%s:%d: writing CSV line failed: %zi/%zu bytes\n", __FILE__, __LINE__, written, line_length);
		rv = EXIT_FAILURE;
		goto ERR_FREE;
	}

ERR_FREE:
	free(line);

ERR:
	return rv;
}


void measurement_print(measurement m) {
	printf("measurements = { ");
	printf("time.tv_sec = %ld; ", m.time.tv_sec);
	printf("time.tv_nsec = %ld; ", m.time.tv_nsec);
	printf("data_c = %zu; ", m.data_c);
	printf("data = { ");
	for (size_t i = 0; m.data_c > i; i++) {
		printf("types[%zu] = %d; ", i, m.types[i]);
		printf("priorities[%zu] = %d; ", i, m.priorities[i]);
		printf("data[%zu] = %lf; ", i, m.data[i]);
	}
	printf("}; ");
	printf("};");
}
sensor.h
/**
 * @file  sensor.h
 * @brief Definitions for all sensor programs.
 *
 * @author Jonas Gaida, Florian Schwarz
 */

#ifndef INCLUDE_SENSOR_H_
#define INCLUDE_SENSOR_H_

#include <time.h>  // struct timespec

#include "measurement.h"  // measurement_data
#include "task.h"  // task
#include "definitions.h"  // phase


/**
 * @brief Possible I2C bus frequencies.
 *
 * Currently only values which are both within the official I2C specification
 * and also part of the valid clock dividers in the bcm2835 library are
 * available.
 */
typedef enum baudrate {
	BAUD_100KHZ = 100000,    ///< The original mode at 100 kHz.
	BAUD_400KHZ = 400000     ///< "Fast-mode" at 400 kHz.

	// Additional frequencies from the I2C specification.
//	BAUD_1000KHZ = 1000000,  ///< "Fast-mode plus" at 1 MHz.
//	BAUD_3400KHZ = 3400000,  ///< "High-speed mode" at 3.4 MHz.
//	BAUD_5000KHZ = 5000000,  ///< "Ultra Fast-mode" at 5 MHz (unidirectional).

	// Additional frequencies from the bcm2835 library based on a 250 MHz clock.
//	BAUD_1666KHZ = 1666667,  ///< BCM2835_I2C_CLOCK_DIVIDER_150
//	BAUD_1689KHZ = 1689189   ///< BCM2835_I2C_CLOCK_DIVIDER_148
} baudrate;

#define MIN_BAUDRATE BAUD_100KHZ
#define MAX_BAUDRATE BAUD_400KHZ


/**
 * @brief Possible sensor states controlled by master
 */
typedef enum s_state {
	UNKNOWN = 0x00,  ///< Default on-start state
	UNREACHABLE,     ///< If first connection failed, never touch s again
	INIT,            ///< If first connection success, init remaining or reconnected
	MEASURE,         ///< If init finished, measure remaining or measure successful
	SLEEP,           ///< If no active phase
	FAILURE          ///< If consecutive failed communications > x, try is_connected all y seconds, if success, init
} s_state;


/**
 * @brief Sensor interface.
 */
typedef struct i2c_sensor_reg {
	/**
	 * @brief Sensor name.
	 *
	 * The name is used for the creation of the data files, error messages and
	 * for easier identification by the user.
	 */
	const char *const name;

	/**
	 * @brief Bit mask containing the phases during which the sensor is active.
	 */
	const phase active_phases;

	/**
	 * @brief Length of a full measurement cycle of this sensor in milliseconds.
	 *
	 * Should not be shorter than sum of measure delays!
	 */
	const unsigned long cycle_len;

	/**
	 * @brief Pointer to array of measurement types
	 *
	 * Needs to be in sync with measurement data
	 */
	const date_type *const types;

	/**
	 * @brief Pointer to array of measurement priorities
	 *
	 * Needs to be in sync with measurement data
	 */
	const signed char *const priorities;

	/**
	 * @brief Amount of data packages to deliver
	 *
	 * Needs to be in sync with measurement data
	 */
	const size_t date_amt;

	/**
	 * @brief Header line for CSV file (containing newline symbol) as
	 *        null-terminated string.
	 */
	const char *const csv_header;

	/**
	 * @brief The maximum frequency of the I2C bus supported by this sensor.
	 */
	const baudrate i2c_freq_max;

	/**
	 * @brief Initialization of the sensor.
	 *
	 * For sensors that need calibration or other initialization.
	 * This function is also called after leaving FAILURE or SLEEP state.
	 * So beware, if some initilizations are dependent of START state.
	 * (e.g. h_offset with launch location height and pressure.)
	 */
	int (*init)(phase p, signed char *const it_rem, long *const processing_time);
	/**
	 * @brief Collecting measure data from sensor.
	 *
	 * Collection is finished if iterations_rem is <= 0.
	 * After state transition master sets it_rem < 0, so sensor can recognize
	 * first entry.
	 *
	 */
	int (*measure)(phase p, measurement *const m, signed char *const it_rem, long *const processing_time);

	/**
	 * @brief I2C-Connectivity-Test to sensor, prior to an initializiation
	 *
	 * Is used in the beginning to determin connected sensors and to test in-flight
	 * failed sensors for reinitialization
	 */
	int (*is_connected)(void);

} i2c_sensor_reg;



/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * !!                                                                         !!
 * !!       REST OF FILE IS NOT PART OF INTERFACE BUT IS USED BY MASTER       !!
 * !!                                                                         !!
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */



/**
 * @brief Sensor struct
 */
typedef struct i2c_sensor {
	/**
	 * @brief Pointer to corresponding task // TODO-REVAL - Necessary?
	 */
	task *t;
	/**
	 * @brief Pointer to original i2c_sensor_reg.
	 */
	const i2c_sensor_reg *reg;
	/**
	 * @brief Sensor state.
	 */
	s_state state;
	/**
	 * @brief Counter for multiple function calls (e.g. init or measure)
	 *
	 * Sensor function will get called additionally as often as this property is set.
	 * After state transition, this gets set to < 0, so sensor can recognize first
	 * entry. Then in first call sensor may set an aproporiate value
	 * (e.g. 2 for two further iterations, so with current in total 3 rounds).
	 * After leaving master checks if this is <= 0 and if true switches to next state.
	 * Decrement is handled by master.
	 */
	signed char it_rem;
	/**
	 * @brief Time which the sensor needs to finish current action
	 */
	long processing_time;
	/**
	 * @brief Time for a complete sensor cycle to redeem s_cycle_len.
	 *
	 * After finishing a full cycle s_cycle_len is added
	 */
	struct timespec next_cycle_time;
	/**
	 * @brief Struct to fill with one measurement round
	 */
	measurement measurements;
	/**
	 * @brief Destination file handler for csv output
	 */
	int csv_file;
	/**
	 * @brief Sequential error counter
	 */
	unsigned char err_seq;
	unsigned long err_total;
	unsigned long failure_total;

} i2c_sensor;


/**
 * @brief Creates i2c_sensor
 *
 * Allocates necessary memory for i2c_sensor struct and sub structures
 * specified by data from s_reg.
 *
 * @param[in] s_reg i2c_sensor_reg, wherefrom to take specification data
 * @param[out] s Pointer to Pointer of i2c_sensor struct
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int sensor_create(const i2c_sensor_reg s_reg, i2c_sensor **s);


/**
 * @brief Creates only i2c_sensor substructures
 *
 * If an array of i2c_sensors already exists, this function may come handy.
 * Allocates necessary memory for i2c_sensor sub structures specified
 * by data from s_reg.
 *
 * @param[in] s_reg i2c_sensor_reg with specifying data for allocation
 * @param[out] s Pointer to i2c_sensor struct
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int sensor_create_sub(const i2c_sensor_reg s_reg, i2c_sensor *s);


/**
 * @brief Initialize i2c_sensor
 *
 * Initializes i2c_sensor by data from s_reg.
 *
 * @param[in] s_reg i2c_sensor_reg, wherefrom to take init data
 * @param[out] s Pointer to i2c_sensor struct
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int sensor_init(const i2c_sensor_reg *const s_reg, i2c_sensor *s);


/**
 * @brief Initialize i2c_sensor, except task pointer and error values
 *
 * Initializes i2c_sensor by data from s_reg.
 *
 * @param[in] s_reg i2c_sensor_reg, wherefrom to take init data
 * @param[out] s Pointer to i2c_sensor struct
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int sensor_reset(const i2c_sensor_reg *const s_reg, i2c_sensor *s);


/**
 * @brief Probes in an i2c_sensor_reg array for connected sensors, allocates necessary recources and initializes them.
 *
 * To probe is_connected() is called for each sensor.
 * Appropriate memory gets allocated for s_con and it gets
 * initialized with given i2c_sensor_reg data.
 *
 * @param[in] s_reg Pointer to i2c_sensor_reg array, which to probe
 * @param[in] amt_s_reg Size of i2c_sensor_reg array s
 * @param[out] s_con Pointer to uninitialized i2c_sensor pointer
 * @param[out] amt_con Pointer to size of i2c_sensor
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int sensor_create_init_connected_sensors(const i2c_sensor_reg *const s_reg, size_t amt_s_reg, i2c_sensor **s_con, size_t *amt_con);


/**
 * @brief Prints info about sensor_reg
 *
 * @param[in] reg i2c_sensor to print
 */
void sensor_reg_print(i2c_sensor_reg reg);


/**
 * @brief Prints info about sensor
 *
 * @param[in] s i2c_sensor to print
 */
void sensor_print(i2c_sensor s);


/**
 * @brief Tests if sensor @p s provides measurements of type @p type
 *
 * @param[in] s i2c_sensor, which to search
 * @param[in] type The wanted date_type
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int sensor_has_date_type(i2c_sensor s, date_type type);


/**
 * @brief Delivers working sensor with highest priority on in type specified date_type
 *
 * If equal priorities, then first match is returned.
 *
 * @param[in] s Pointer to i2c_sensor array, which to search
 * @param[in] amt Size of i2c_sensor array s
 * @param[in] type The wanted date_type
 * @param[out] sensor Pointer to working height sensor with highest priority
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int sensor_get_best_by_date_type(i2c_sensor *s, size_t amt, date_type type, i2c_sensor **sensor);


#endif  // INCLUDE_SENSOR_H_
sensor.c
/**
 * @file  sensor.c
 * @brief Implementation of sensor.h
 *
 * @author Florian Schwarz
 */

#include "sensor.h"

#include <stdlib.h>  // exit codes
#include <stdio.h> // fprintf
#include <errno.h>  // errno
#include <string.h>  // strerror

int sensor_create(const i2c_sensor_reg s_reg, i2c_sensor **s) {
	int rv = EXIT_SUCCESS;
	int rv_f;
	if (NULL == s) {
		fprintf(stderr, "%s:%i: Input s is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	// Allocate i2c and let the rest sensor_create_sub() do
	errno = 0;
	*s = malloc(sizeof (i2c_sensor));
	if (NULL == (*s) && ENOMEM == errno) {
		fprintf(stderr, "%s:%i: malloc failed: %s\n", __FILE__, __LINE__, strerror(errno));
		return EXIT_FAILURE;
	}
	rv_f = sensor_create_sub(s_reg, *s);
	if (EXIT_FAILURE == rv_f) {
		fprintf(stderr, "%s:%i: sensor_create_sub failed\n", __FILE__, __LINE__);
		rv = EXIT_FAILURE;

	}

	if (EXIT_FAILURE == rv) {
		free(*s);
	}
	return rv;
}

int sensor_create_sub(const i2c_sensor_reg s_reg, i2c_sensor *s) {
	if (NULL == s) {
		fprintf(stderr, "%s:%i: Input s is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	// Catch faulty registered sensors

	if (0 == s_reg.cycle_len) {
		fprintf(stderr, "%s:%i: Sensor %s cycle_len is 0\n", __FILE__, __LINE__, s_reg.name);
		return EXIT_FAILURE;
	}
	if (0 == s_reg.active_phases) {
		fprintf(stderr, "%s:%i: Sensor %s has no active_phases\n", __FILE__, __LINE__, s_reg.name);
		return EXIT_FAILURE;
	}

	// Allocate measurement
	if (EXIT_FAILURE == measurement_create(&(s->measurements), s_reg.date_amt, s_reg.types, s_reg.priorities)) {
		fprintf(stderr, "%s:%i: measurement_create failed\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}


int sensor_init(const i2c_sensor_reg *const s_reg, i2c_sensor *s) {
	if (NULL == s_reg) {
		fprintf(stderr, "%s:%i: Input s_reg is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == s) {
		fprintf(stderr, "%s:%i: Input s is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	s->t = NULL;
	sensor_reset(s_reg, s);
	s->err_seq = 0;
	s->err_total = 0;
	s->failure_total = 0;

	return EXIT_SUCCESS;
}


int sensor_reset(const i2c_sensor_reg *const s_reg, i2c_sensor *s) {
	if (NULL == s_reg) {
		fprintf(stderr, "%s:%i: Input s_reg is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == s) {
		fprintf(stderr, "%s:%i: Input s is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	s->reg = s_reg;
	s->state = INIT;
	s->it_rem = -1; // Default value, so sensor can recognize state entry
	s->processing_time = 0;
	s->csv_file = 0;

	return EXIT_SUCCESS;
}


int sensor_create_init_connected_sensors(const i2c_sensor_reg *const s_reg, size_t amt_s_reg, i2c_sensor **s_con, size_t *amt_con) {
	int rv = EXIT_SUCCESS;
	char *connected;
	size_t s_con_pos;
	size_t cnt_failed;

	if (NULL == s_reg) {
		fprintf(stderr, "%s:%i: Input s is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (0 == amt_s_reg) {
		fprintf(stderr, "%s:%i: Input amt is 0\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == s_con) {
		fprintf(stderr, "%s:%i: Input s_con is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == amt_con) {
		fprintf(stderr, "%s:%i: Input amt_con is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	// Determine size and position of connected sensors
	*amt_con = 0;
	errno = 0;
	connected = calloc(amt_s_reg, sizeof (char));
	if (NULL == connected && ENOMEM == errno) {
		fprintf(stderr, "%s:%i: calloc failed: %s\n", __FILE__, __LINE__, strerror(errno));
		return EXIT_FAILURE;
	}
	for (size_t i = 0; amt_s_reg > i; i++) {
		if (EXIT_SUCCESS == s_reg[i].is_connected()) {
			connected[i] = 1;
			(*amt_con)++;
		}
	}

	// Allocate i2c_sensor_array and init every connected
	errno = 0;
	*s_con = malloc((*amt_con) * sizeof (i2c_sensor));
	if (NULL == (*s_con) && ENOMEM == errno) {
		fprintf(stderr, "%s:%i: malloc failed: %s\n", __FILE__, __LINE__, strerror(errno));
		rv = EXIT_FAILURE;
		goto ERR_PROBE_CONNECTED_SENSORS_1;
	}

	s_con_pos = 0;
	cnt_failed = 0;
	for (size_t i = 0; amt_s_reg > i; i++) {
		if(connected[i]) {
			// Create
			if (EXIT_FAILURE == sensor_create_sub(s_reg[i], &((*s_con)[s_con_pos]))) {
				fprintf(stderr, "%s:%i: Warning: sensor_create_sub failed, therefore sensor was dropped\n", __FILE__, __LINE__);
				cnt_failed++;
				continue;
			}
			// Init
			if (EXIT_FAILURE == sensor_init(&(s_reg[i]), &((*s_con)[s_con_pos]))) {
				fprintf(stderr, "%s:%i: Warning: sensor_init failed, therefore sensor was dropped\n", __FILE__, __LINE__);
				cnt_failed++;
				continue;
			}
			s_con_pos++;
		}
	}
	// If any sensor failed to create or init, try to reallocate if any sensor left
	if (0 != cnt_failed) {
		(*amt_con) -= cnt_failed;
		if (0 == (*amt_con)) {
			fprintf(stderr, "%s:%i: All connected sensors failed in sensor_create_sub\n", __FILE__, __LINE__);
			rv = EXIT_FAILURE;
			goto ERR_PROBE_CONNECTED_SENSORS_1;
		}
		errno = 0;
		*s_con = realloc((*s_con), (*amt_con) * sizeof (i2c_sensor));
		if (NULL == (*s_con) && ENOMEM == errno) {
			fprintf(stderr, "%s:%i: realloc failed: %s\n", __FILE__, __LINE__, strerror(errno));
			rv = EXIT_FAILURE;
			goto ERR_PROBE_CONNECTED_SENSORS_2;
		}
	}

	if (EXIT_FAILURE == rv) {
ERR_PROBE_CONNECTED_SENSORS_2:
		for (int i = s_con_pos-1; 0 <= i; i--) {
			measurement_destroy(&((*s_con)[i].measurements));
		}
		free(*s_con);
	}
ERR_PROBE_CONNECTED_SENSORS_1:
	free(connected);

	return rv;
}


void sensor_reg_print(i2c_sensor_reg reg) {
	printf("sensor_reg = { ");
	printf("name = %s; ", reg.name);
	printf("active_phases = %d; ", reg.active_phases);
	printf("cycle_len = %ld; ", reg.cycle_len);
	for (size_t i = 0; reg.date_amt > i; i++) {
		printf("types[%zu] = %d; ", i, reg.types[i]);
		printf("priorities[%zu] = %d; ", i, reg.priorities[i]);
	}
	printf("csv_header = %s", reg.csv_header);
	printf(" i2c_freq_max = %d; ", reg.i2c_freq_max);
	printf("};");
}


void sensor_print(i2c_sensor s) {
	printf("sensor = { ");
	if(NULL != s.t) {
		task_print(*(s.t));
	} else {
		printf("task = { NULL }");
	}
	printf(" reg = %p;", (const void *)s.reg);
	printf(" state = %d;", s.state);
	printf(" it_rem = %d;", s.it_rem);
	printf(" processing_time = %ld;", s.processing_time);
	printf(" next_cycle_time.tv_sec = %ld;", s.next_cycle_time.tv_sec);
	printf(" next_cycle_time.tv_nsec = %ld; ", s.next_cycle_time.tv_nsec);
	measurement_print(s.measurements);
	printf(" csv_file = %d; ", s.csv_file);
	printf(" err_seq = %d; ", s.err_seq);
	printf(" err_total = %ld; ", s.err_total);
	printf(" failure_total = %ld; ", s.failure_total);
	if(NULL != s.reg) {
		sensor_reg_print(*(s.reg));
	} else {
		printf("reg = { NULL }");
	}
	printf("};");
}


int sensor_has_date_type(i2c_sensor s, date_type type) {
	for (size_t i = 0; s.reg->date_amt > i; i++) {
		if (type == s.reg->types[i]) {
			return EXIT_SUCCESS;
		}
	}
	return EXIT_FAILURE;
}


int sensor_get_best_by_date_type(i2c_sensor *s, size_t amt, date_type type, i2c_sensor **sensor) {
	// Check input
	if (NULL == s) {
		fprintf(stderr, "%s:%i: s is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (0 == amt) {
		fprintf(stderr, "%s:%i: amt is 0\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	*sensor = NULL;
	signed char prio = -128;
	for (size_t i = 0; amt > i; i++) {
		if (INIT == s[i].state || MEASURE == s[i].state) { // Sensor running?
			for (size_t j = 0; s[i].reg->date_amt > j; j++) { // Look into each date
				if ((type == s[i].reg->types[j]) && (prio < s[i].reg->priorities[j])) { // Right type and higher priority?
					*sensor = &(s[i]);
					prio = s[i].reg->priorities[j];
				}
			}	
// TODO-Check - For some reason, s[i].measurements.types[j] isn't stable
//			for (size_t j = 0; s[i].measurements.data_c > j; j++) { // Look into each measurement
//				if (type == s[i].measurements.types[j] && prio < s[i].measurements.priorities[j]) { // Right type and higher priority?
//					*sensor = &(s[i]);
//					prio = s[i].measurements.priorities[j];
//				}
//			}	
		}
	}
	if (NULL == *sensor) {
		fprintf(stderr, "%s:%d: no matching sensor found\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}
task.h
/**
 * @file  task.h
 * @brief Tasks for master program
 *
 * @author Florian Schwarz
 */

#ifndef INCLUDE_TASK_H_
#define INCLUDE_TASK_H_

#include <time.h>  // struct timespec

#include "definitions.h"  // phase


/**
 * @brief Task state
 */
typedef enum task_state {
	RUNNING,
	STOPPED
} task_state;

/**
 * @brief Task type
 *
 * Used as switch argument in schedule loop.
 * So except for SENSOR use only one task per type
 */
typedef enum task_type {
	SENSOR,
	LED_MASTER,
	LED_SENSORS,
	CAM_USB_TASK,
	CAM_RASPI_TASK,
	GSM_TASK,
	WATCHDOG,
	WATCHPUPPY,
	FALLBACK_STATE_TO_RECOVERY_CHANGER,
	TASK_TYPE_AMT					///< Insert new tasks before this one
} task_type;

/**
 * @brief Represents a task.
 *
 * Used for scheduling.
 */
typedef struct task {
	task_type type;
	task_state state;			///< sleeping wont get touched
	phase active_phases;		///<
	struct timespec	next_time;	///< next execution time
	void *p;					///< Currently used to store pointer to sensor struct
} task;

/**
 * @brief Prints info about task struct
 *
 * @param[in] t task struct to print
 */
void task_print(task t);


/**
 * @brief Delivers next not STOPPED task.
 *
 * Task with smallest next_time and not STOPPED state is returned.
 * If there are multiple tasks with same next_time, the first one
 * is returned.
 *
 * @param[in] t Pointer to task array, which to search
 * @param[in] amt Size of task array t
 * @param[out] next Pointer to task with smallest next_time
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int task_get_next(task *t, size_t amt, task **next);


/**
 * @brief Updates task state on all tasks dependening on their active_phases
 *
 * @param[in,out] tasks Pointer to task array, which to update
 * @param[in] amt Size of task array tasks
 * @param[in] cur_phase Current phase
 *
 * @return @c EXIT_SUCCESS if successful else @c EXIT_FAILURE.
 */
int task_update_states_by_phase(task *tasks, size_t amt, phase cur_phase);


#endif  // INCLUDE_TASK_H_
task.c
/**
 * @file  task.c
 * @brief Implementation of task.h
 *
 * @author Florian Schwarz
 */

#include "task.h"

#include <stdlib.h>  // exit codes
#include <stdio.h> // fprintf
#include "timespec_helper.h" // timespec_lt


void task_print(task t) {
	printf("task = { ");
	printf("type = %d; ", t.type);
	printf("state = %d; ", t.state);
	printf("next_time.tv_sec = %ld; ", t.next_time.tv_sec);
	printf("next_time.tv_nsec = %ld; ", t.next_time.tv_nsec);
	printf("p = 0x%p; ", t.p);
	printf("};");
}


int task_get_next(task * t, size_t amt, task ** next) {
	if (NULL == t) {
		fprintf(stderr, "%s:%i: Input t is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (0 == amt) {
		fprintf(stderr, "%s:%i: Input amt is 0\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}
	if (NULL == next) {
		fprintf(stderr, "%s:%i: Input next is NULL\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	// Search first not-stopped comparator task
	size_t i_next = amt;
	for (size_t i = 0; amt > i; i++) {
		if (STOPPED != t[i].state) {
			i_next = i;
			break;
		}
	}
	if (amt == i_next) {
		fprintf(stderr, "%s:%i: all tasks stopped\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	// Find smallest
	for (size_t i = i_next + 1; amt > i; i++) {
		if (STOPPED == t[i].state) {
			continue;
		}
		if (timespec_lt(t[i].next_time, t[i_next].next_time)) {
			i_next = i;
		}
	}
	*next = &(t[i_next]);

	return EXIT_SUCCESS;
}


int task_update_states_by_phase(task *tasks, size_t amt, phase cur_phase) {
	if (NULL == tasks) {
		fprintf(stderr, "%s:%s:%i: Input tasks is NULL\n", __FILE__, __func__, __LINE__);
		return EXIT_FAILURE;
	}

	for (size_t i = 0; amt > i; i++) {
		if (0 != (tasks[i].active_phases & cur_phase)) { // Active phase for task?
			tasks[i].state = RUNNING;
		} else {
			tasks[i].state = STOPPED;
		}
	}
	return EXIT_SUCCESS;
}
sensor_tmp117.h
/**
 * @file  sensor_tmp117.h
 * @brief Definitions for the TMP117 sensor.
 *
 * @author Florian Schwarz
 */

#ifndef INCLUDE_SENSORTMP117_H_
#define INCLUDE_SENSORTMP117_H_

#include "sensor.h"


/*
 * Implementations of the functions defined in sensor.h.
 */
int tmp117_init(phase p, signed char *const it_rem, long *const processing_time_rem);
int tmp117_measure(phase p, measurement *const m, signed char *const it_rem, long *const processing_time_rem);
int tmp117_is_connected(void);

static const date_type TMP117_TYPES[] = {TEMPERATURE};
static signed char TMP117_PRIORITIES[] = {1};

static const size_t TMP117_DATE_AMT = sizeof TMP117_TYPES / sizeof (date_type);

/**
 * @brief Registration data for a TMP117 sensor.
 */
static const i2c_sensor_reg TMP117 = {
	"TMP117",
	START | FLIGHT,
	500,
	TMP117_TYPES,
	TMP117_PRIORITIES,
	TMP117_DATE_AMT,
	"\"Unix Time (s)\";\"Temperature inside (°C)\"\n",
	BAUD_400KHZ,
	tmp117_init,
	tmp117_measure,
	tmp117_is_connected
};

#endif  // INCLUDE_SENSORTMP117_H_
sensor_tmp117.c
/**
 * @file  sensor_tmp117.c
 * @brief Implementation of sensor_tmp117.h
 *
 * @author Florian Schwarz
 *
 * some sensor specs:
 * - aimed range -55 °C to 125 °C (accuracy (min, typ, max): -0.25, +-0.1, 0.25)
 * - i2c addresses: 0x48 - 0x4B
 * - data format: 16 Bit, MSB first
 * - temperature in two's complement, decimal place between bit 8 and 7
 * - a "one-shot" measurement is triggered by writing 11 into MOD bits in configuration register
 *
 * some implementation decisions:
 * - ignored intergrated EEPROM for sensor intitialization for easier sensor displacement
 * - during power-on reset (POR) EEPROM is blocking configuration register for approx. 1.5 ms. This is handled by this implementation
 * - one-shot instead of continous mode chosen, because the latter isn't fitting to time requirements
 * - averaging disabled, because it needs at least 125 ms, which may get in conflict with requirements of minimum measurement frequency
 *
 */

#include "sensor_tmp117.h"

#include <stdlib.h>  // exit codes
#include <stdint.h>  // uint8_t, uint32_t
#include <stdio.h>  // fprintf
#include <string.h>  // strerror
#include <time.h>  // clock_gettime
#include <errno.h>  // errno

#include "bcm2835.h"


/*
 * Important values only for this sensor.
 */
static const uint8_t I2C_ADDR = 0x48;
static const char REG_ADDR_TEMP = 0x00;
static const char REG_ADDR_CONF = 0x01;
static const char REG_ADDR_TEMP_OFFS = 0x07;

static const uint8_t DATA_READY_BIT_POS = 13;
static const uint8_t MOD_BIT_POS = 10;
static const uint8_t AVG_BIT_POS = 5;
static const uint16_t MOD_SHUTDOWN = 0x0001 << MOD_BIT_POS;
static const uint16_t MOD_ONE_SHOT = 0x0003 << MOD_BIT_POS;
static const uint16_t AVG_DISABLED = 0x0000 << AVG_BIT_POS;

static const uint16_t CONF_REG = MOD_SHUTDOWN | AVG_DISABLED;
static const uint16_t TEMP_OFFS_REG = 0x0000;

static const uint16_t EEPROM_BUSY_MASK = 0x1000;

static const long EEPROM_BUSY_TIME= 2; // wait time in ms if EEPROM is blocking config registers
static const long ADC_TIME= 18;

static const uint32_t BUFFER_LENGTH = 3;


/*
 * 1. Check for blocked configuration register via EEPROM_Busy status flag
 * 2. Set configuration register with CONF_REG
 * 3. Set temperature offset register with TEMP_OFFS_REG
 */
int tmp117_init(__attribute__((unused)) phase p, signed char *const it_rem, long *const processing_time_rem) {

	const uint8_t INIT_ITERATIONS = 2; // Need to be synced with switch case!

	(void) p;
	uint8_t rc;
	char buf[BUFFER_LENGTH];
	uint16_t reg;

	// Set it_rem if first entry
	if(0 > *it_rem) {
		*it_rem = INIT_ITERATIONS-1;
	}

	bcm2835_i2c_setSlaveAddress(I2C_ADDR);

	switch (*it_rem) {
		case 1:
			// 1. Check for blocked configuration register via EEPROM_Busy status flag

			// Read from configuration register
			// Set register pointer
			rc = bcm2835_i2c_write(&REG_ADDR_CONF, 1);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: writing target register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}
			// Read register
			rc = bcm2835_i2c_read(buf, 2);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: reading from register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}

			reg = (buf[0] << 8) | buf[1];

			// If EEPROM is blocking configuration register, wait for finishing
			if (1 == (reg & EEPROM_BUSY_MASK)) {
				*processing_time_rem = EEPROM_BUSY_TIME;
			} else {
				*processing_time_rem = 0;
			}
			break;

		case 0:

			// 2. Set Configuration register with CONF_REG

			buf[0] = REG_ADDR_CONF;
			buf[1] = (CONF_REG >> 8) & 0xFF;
			buf[2] = CONF_REG & 0XFF;
			// Set register pointer and write to register
			rc = bcm2835_i2c_write(buf, 3);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: writing target register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}

			// Read configuration register for validation
			rc = bcm2835_i2c_read(buf, 2);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: reading from register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}
			reg = (buf[0] << 8) | buf[1];
			if (CONF_REG != reg) {
				fprintf(stderr, "%s:%d: configuration validation failed: read 0x%04x, but expected 0x%04x\n", __FILE__, __LINE__, reg, CONF_REG);
				return EXIT_FAILURE;
			}

			// 3. Set temperature offset register with TEMP_OFFS_REG
			buf[0] = REG_ADDR_TEMP_OFFS;
			buf[1] = (TEMP_OFFS_REG >> 8) & 0xFF;
			buf[2] = TEMP_OFFS_REG & 0XFF;
			// Set register pointer and write to register
			rc = bcm2835_i2c_write(buf, 3);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: writing target register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}

			// Read temperature offset register for validation
			rc = bcm2835_i2c_read(buf, 2);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: reading from register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}
			reg = (buf[0] << 8) | buf[1];
			if (TEMP_OFFS_REG != reg) {
				fprintf(stderr, "%s:%d: temperature offset validation failed: read 0x%04x, but expected 0x%04x\n", __FILE__, __LINE__, reg, TEMP_OFFS_REG);
				return EXIT_FAILURE;
			}

			*processing_time_rem = 0;
			break;
	}

	return EXIT_SUCCESS;
}


int tmp117_measure(__attribute__((unused)) phase p, measurement *const m, signed char *const it_rem, long *const processing_time_rem) {

	const uint8_t MEASURE_ITERATIONS = 2; // Need to be synced with switch case!

	char buf[BUFFER_LENGTH];
	int8_t rc;
	uint16_t reg;

	if(0 > *it_rem) {
		*it_rem = MEASURE_ITERATIONS-1;
	}

	bcm2835_i2c_setSlaveAddress(I2C_ADDR);

	switch (*it_rem) {
		case 1:
			// Acquire timestamp.
			if (0 > clock_gettime(CLOCK_REALTIME, &(m->time))) {
				fprintf(stderr, "%s:%d: acquiring measurement timestamp failed: %s\n", __FILE__, __LINE__, strerror(errno));
				return EXIT_FAILURE;
			}

			// One-Shot Conversion
			buf[0] = REG_ADDR_CONF;
			buf[1] = (MOD_ONE_SHOT >> 8) & 0xFF;
			buf[2] = MOD_ONE_SHOT & 0XFF;
			// Set register pointer and write to register
			rc = bcm2835_i2c_write(buf, 3);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: writing target register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}

			*processing_time_rem = ADC_TIME;
			break;

		case 0:
			// 1. Check for data_ready

			// Read from configuration register
			// Set register pointer
			rc = bcm2835_i2c_write(&REG_ADDR_CONF, 1);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: writing target register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}
			// Read register
			rc = bcm2835_i2c_read(buf, 2);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: reading from register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}

			reg = (buf[0] << 8) | buf[1];
			if (0 == ((reg >> DATA_READY_BIT_POS) & 0x0001)) {
				fprintf(stderr, "%s:%d: no temperature data ready\n", __FILE__, __LINE__);
				return EXIT_FAILURE;
			}

			// 2. Read from temperature register

			// Set register pointer
			rc = bcm2835_i2c_write(&REG_ADDR_TEMP, 1);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: writing target register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}
			// Read register
			rc = bcm2835_i2c_read(buf, 2);
			if (BCM2835_I2C_REASON_OK != rc) {
				fprintf(stderr, "%s:%i: reading from register %0#2hhx failed: reason code %0#2hhx\n", __FILE__, __LINE__, REG_ADDR_TEMP, rc);
				return EXIT_FAILURE;
			}

			// 3. Convert data to Kelvin.

			if (0x80 == (buf[0] & 0x80)) {
				// Negativ temperature
				buf[0] = buf[0] & 0x7F; // Clear SIGN
				m->data[0] = buf[0] * 2 + buf[1] / 128.0 - 256; // Shift bits to correct position
			}
			else {
				// Postive temperature
				m->data[0] = buf[0] * 2 + buf[1] / 128.0; // Shift bits to correct position
			}

			*processing_time_rem = 0;
			break;
	}
	return EXIT_SUCCESS;
}


int tmp117_is_connected(void) {
	bcm2835_i2c_setSlaveAddress(I2C_ADDR);
	return bcm2835_i2c_write(NULL, 0);
}
camera_raspicam.h
/**
 * @file  camera_raspicam.h
 * @brief Definitions for the Raspi PI camera module
 *
 * @author Arne Müller
 */

#ifndef INCLUDE_CAMERARASPICAM_H_
#define INCLUDE_CAMERARASPICAM_H_

#include "camera.h"


int raspicam_start(camera_mode mode, const char *filename, camera_parameters parameters);
int raspicam_stop(void);

/**
 * @brief Initializer for a Raspberry PI camera moule
 */
#define RASPICAM {raspicam_start, raspicam_stop}

#endif  // INCLUDE_CAMERARASPICAM_H_
camera_raspicam.c
/**
 * @file  camera_raspicam.c
 * @brief Implementation of camera_raspicam.h
 *
 * @author Arne Müller
 */

#include "camera_raspicam.h"

#include <stdio.h>
#include <stdlib.h>



static const unsigned char CAM_NUM = 0;

static const unsigned char IMAGE_JPG_QUALITY = 95;  // Must be between 0 and 100.

static const unsigned long VIDEO_SEGMENT_MINUTES = 15;
static const double VIDEO_BITRATE_MB = 18.0;  // Must be below 25.

static camera_mode cur_mode;



/**
 * Full documentation for the commands used: https://www.raspberrypi.org/documentation/raspbian/applications/camera.md
 */
int raspicam_start(camera_mode mode, const char *filename, camera_parameters parameters) {

	char command[512];
	const size_t COMMAND_LEN = sizeof command / sizeof command[0];
	int tmp = -1;

	// Times are required in milliseconds.
	parameters.duration *= 1000;
	parameters.frequency *= 1000;

	cur_mode = mode;

	if (PHOTO == mode) {

		/* raspistill
		 * --width      *          < Set the image width.
		 * --height     *          < Set the image height.
		 * --encoding   jpg        < Use the hardware-accelerated JPEG encoding.
		 * --quality    *          < Set the JPEG image quality.
		 * --exif       none       < Use no EXIF tags.
		 * --timeout    *          < Set the duration of the image series (in milliseconds).
		 * --timelapse  *          < Set the duration between single images in the series.
		 * --camselect  *          < Set the recording camera in a multi-camera setup.
		 * --framestart 0          < Set the number of the first image of the series (for use in the file name).
		 * --nopreview             < Disable a live preview to a connected display.
		 * --output     *_%05d.jpg < Set the filename of the images. A five digit index number gets appended.
		 */
		tmp = snprintf(command, COMMAND_LEN, "%s%hu%s%hu%s%hu%s%lu%s%lu%s%d%s%s%s",
			"raspistill --width ", parameters.width, " --height ", parameters.height,
			" --encoding jpg --quality ", IMAGE_JPG_QUALITY, " --exif none --timeout ", parameters.duration,
			" --timelapse ", parameters.frequency,
			" --camselect ", CAM_NUM,
			" --framestart 0 --nopreview --output ", filename, "_%05d.jpg &"
		);
		if (0 > tmp) {
			fprintf(stderr, "%s:%i: writing raspistill command to buffer failed\n", __FILE__, __LINE__);
			return EXIT_FAILURE;
		} else if (COMMAND_LEN <= (size_t) tmp) {
			fprintf(stderr, "%s:%i: writing raspistill command to buffer failed: %i bytes needed, got %zu\n", __FILE__, __LINE__, tmp, COMMAND_LEN);
			return EXIT_FAILURE;
		}
	} else if (VIDEO == mode) {

		/* raspivid
		 * --width      *           < Set the image width.
		 * --height     *           < Set the image height.
		 * --bitrate    *           < Set the recording bitrate.
		 * --framerate  *           < Set the recording framerate.
		 * --intra      *           < Set the frequency of i-frames.
		 * --codec      H264        < Use the hardware-accelerated H264 encoder.
		 * --profile    high        < Use the "high" preset of the H264 encoder.
		 * --level      4           < Use the encoding level 4 of the H264 encoder.
		 * --inline                 < Generate PPS and SPS headers for every i-frame.
		 * --spstimings             < Insert timing information into SPS blocks.
		 * --flush                  < Disable caching of video data after writing (decreases latency).
		 * --timeout    *           < Set the duration of the recording (in milliseconds).
		 * --camselect  *           < Set the recording camera in a multi-camera setup.
		 * --segment    *           < Set the time after which the recording is split into another file (in milliseconds).
		 * --start      0           < Set the number of the first segment of the recording (for use in the file name).
		 * --nopreview              < Disable a live preview to a connected display.
		 * --output     *_%05d.h264 < Set the filename of the recording. A five digit index number gets appended.
		 */
		tmp = snprintf(command, sizeof command / sizeof command[0], "%s%hu%s%hu%s%lu%s%hu%s%hu%s%lu%s%d%s%lu%s%s%s",
			"raspivid --width ", parameters.width, " --height ", parameters.height,
			" --bitrate ", (unsigned long) (VIDEO_BITRATE_MB * 1000000), " --framerate ", parameters.framerate,
			" --intra ", parameters.framerate,
			" --codec H264 --profile high --level 4 --inline --spstimings --flush --timeout ", parameters.duration,
			" --camselect ", CAM_NUM,
			" --segment ", VIDEO_SEGMENT_MINUTES * 60 * 1000, " --start 0 --nopreview --output ", filename, "_%H-%M-%S.h264 &"
		);
		if (0 > tmp) {
			fprintf(stderr, "%s:%i: writing raspivid command to buffer failed\n", __FILE__, __LINE__);
			return EXIT_FAILURE;
		} else if (COMMAND_LEN <= (size_t) tmp) {
			fprintf(stderr, "%s:%i: writing raspivid command to buffer failed: %i bytes needed, got %zu\n", __FILE__, __LINE__, tmp, COMMAND_LEN);
			return EXIT_FAILURE;
		}
	}

	return system(command);
}

int raspicam_stop(void) {
	if (PHOTO == cur_mode) {
		return system("killall raspistill");
	}
	return system("killall raspivid");
}
task_watchdog.h
/**
 * @file  task_watchdog.h
 * @brief Definitions for the watchdog.
 *
 * @author Jonas Gaida
 */

#ifndef INCLUDE_TASKWATCHDOG_H_
#define INCLUDE_TASKWATCHDOG_H_



void wake_watchdog(void);

#endif  // INCLUDE_TASKWATCHDOG_H_
task_watchdog.c
/**
 * @file  task_watchdog.c
 * @brief Implementation of task_watchdog.h
 *
 * @author Jonas Gaida
 */

#include "task_watchdog.h"

#include "bcm2835.h"



//static const unsigned int PIN_SET0 = 26;
//static const unsigned int PIN_SET1 = 19;
static const unsigned int PIN_WDI = 16;

static unsigned char wdi_on = 0;



void wake_watchdog(void) {
	// TODO: Pins are set to HIGH by default.
	// Should configure 60 second intervals.
//	bcm2835_gpio_set(PIN_SET0);
//	bcm2835_gpio_set(PIN_SET1);

	bcm2835_gpio_fsel(PIN_WDI, BCM2835_GPIO_FSEL_OUTP);

	wdi_on = !wdi_on;
	if (wdi_on) {
		bcm2835_gpio_set(PIN_WDI);
	} else {
		bcm2835_gpio_clr(PIN_WDI);
	}
}

Hierbei handelt es sich nur um Auszüge unserer Implementierung, um die Vorgehensweisen zu verdeutlichen. Der vollständige Quellcode ist unter Quellcode ersichtlich.

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.

Main.java
import Factories.*;
import org.jetbrains.annotations.NotNull;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.List;

public class Main extends ApplicationFrame {

	JSONParser parser;
	JSONObject memory;
	Map<JTextField, JRadioButton> associations; // Needed to associate textfields with the type of input they're pointing towards
	List<WeatherChartFactory> charts;

	private JPanel panel;
	private JButton buttonCreateCharts;
	private JButton buttonChooseHgt;
	private JButton buttonChooseTemp;
	private JButton buttonChoosePress;
	private JButton buttonChooseAcc;
	private JButton buttonChooseGyro;
	private JButton buttonChooseHum;
	private JButton buttonChooseMag;
	private JButton buttonChooseRad;
	private JTextField textFieldHgt;
	private JTextField textFieldTemp;
	private JTextField textFieldPress;
	private JTextField textFieldAcc;
	private JTextField textFieldGyro;
	private JTextField textFieldHum;
	private JTextField textFieldMag;
	private JTextField textFieldRad;
	private JTextField textFieldXMin;
	private JTextField textFieldXMax;
	private JTextField textFieldYMin;
	private JTextField textFieldYMax;
	private JCheckBox checkBoxHgt;
	private JCheckBox checkBoxTemp;
	private JCheckBox checkBoxPress;
	private JCheckBox checkBoxAcc;
	private JCheckBox checkBoxGyro;
	private JCheckBox checkBoxHum;
	private JCheckBox checkBoxMag;
	private JCheckBox checkBoxRad;
	private JRadioButton radioButtonMulti;
	private JRadioButton radioButtonSingle;
	private JRadioButton radioButtonHgt;
	private JRadioButton radioButtonTemp;
	private JRadioButton radioButtonPress;
	private JRadioButton radioButtonAcc;
	private JRadioButton radioButtonGyro;
	private JRadioButton radioButtonHum;
	private JRadioButton radioButtonMag;
	private JRadioButton radioButtonRad;
	private JRadioButton radioButtonTime;

	/*
	 * TODO
	 *  - OrientationChartFactory
	 */
	public Main() {

		super("Wetterballon Auswertung");

		parser = new JSONParser();
		memory = new JSONObject();
		associations = new HashMap<>();
		charts = new ArrayList<>();

		associations.put(textFieldHgt, radioButtonHgt);
		associations.put(textFieldTemp, radioButtonTemp);
		associations.put(textFieldPress, radioButtonPress);
		associations.put(textFieldAcc, radioButtonAcc);
		associations.put(textFieldGyro, radioButtonGyro);
		associations.put(textFieldHum, radioButtonHum);
		associations.put(textFieldMag, radioButtonMag);
		associations.put(textFieldRad, radioButtonRad);

		// Insert last selected filepaths where known
		try {

			memory = (JSONObject) parser.parse(Files.readString(Paths.get("inputFiles.json")));

			textFieldHgt.setText(memory.get("Altitude").toString());
			textFieldTemp.setText(memory.get("Temperature").toString());
			textFieldPress.setText(memory.get("Pressure").toString());
			textFieldAcc.setText(memory.get("Acceleration Z").toString());
			textFieldGyro.setText(memory.get("Rotation Z").toString());
			textFieldHum.setText(memory.get("Humidity").toString());
			textFieldMag.setText(memory.get("Magnetic Flux Density X").toString());
			textFieldRad.setText(memory.get("Irradiance").toString());
		} catch (NoSuchFileException | NullPointerException ignored) {
		} // If the file doesn't exist, do nothing
		catch (Exception e) {
			e.printStackTrace();
		}

		// UI Building
		ButtonGroup singleOrMulti = new ButtonGroup();
		singleOrMulti.add(radioButtonSingle);
		singleOrMulti.add(radioButtonMulti);

		ButtonGroup typeXSelector = new ButtonGroup();
		typeXSelector.add(radioButtonHgt);
		typeXSelector.add(radioButtonTemp);
		typeXSelector.add(radioButtonPress);
		typeXSelector.add(radioButtonAcc);
		typeXSelector.add(radioButtonGyro);
		typeXSelector.add(radioButtonHum);
		//typeXSelector.add(radioButtonLum);
		typeXSelector.add(radioButtonMag);
		typeXSelector.add(radioButtonRad);
		typeXSelector.add(radioButtonTime);

		buttonCreateCharts.addActionListener(e -> {

			String typeX = typeXSelector.getSelection().getActionCommand();
			double xMin, xMax;
			float yMin, yMax;

			try {
				xMin = Double.parseDouble(textFieldXMin.getText());
			} catch (NumberFormatException n) {
				xMin = -Double.MAX_VALUE; // Double.MIN_VALUE is the smallest POSITIV nonzero value
			}

			try {
				xMax = Double.parseDouble(textFieldXMax.getText());
			} catch (NumberFormatException n) {
				xMax = Double.MAX_VALUE;
			}

			try {
				yMin = Float.parseFloat(textFieldYMin.getText());
			} catch (NumberFormatException n) {
				yMin = -Float.MAX_VALUE; // Float.MIN_VALUE is the smallest POSITIV nonzero value
			}

			try {
				yMax = Float.parseFloat(textFieldYMax.getText());
			} catch (NumberFormatException n) {
				yMax = Float.MAX_VALUE;
			}

			try {
				if (checkBoxHgt.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new HeightChartFactory(typeX, readData(textFieldHgt.getText(), "Altitude", xMin, xMax, yMin, yMax)));
					else
						charts.add(new HeightChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldHgt.getText(), typeX, "Altitude", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			try {
				if (checkBoxTemp.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new TemperatureChartFactory(typeX, readData(textFieldTemp.getText(), "Temperature", xMin, xMax, yMin, yMax)));
					else
						charts.add(new TemperatureChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldTemp.getText(), typeX, "Temperature", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			try {
				if (checkBoxPress.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new PressureChartFactory(typeX, readData(textFieldPress.getText(), "Pressure", xMin, xMax, yMin, yMax)));
					else
						charts.add(new PressureChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldPress.getText(), typeX, "Pressure", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			try {
				if (checkBoxAcc.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new AccelerationChartFactory(typeX, readData(textFieldAcc.getText(), "Acceleration Z", xMin, xMax, yMin, yMax)));
					else
						charts.add(new AccelerationChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldAcc.getText(), typeX, "Acceleration Z", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			try {
				if (checkBoxGyro.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new OrientationChartFactory(typeX, readData(textFieldGyro.getText(), "Rotation Z", xMin, xMax, yMin, yMax)));
					else
						charts.add(new OrientationChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldGyro.getText(), typeX, "Rotation Z", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			try {
				if (checkBoxHum.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new HumidityChartFactory(typeX, readData(textFieldHum.getText(), "Humidity", xMin, xMax, yMin, yMax)));
					else
						charts.add(new HumidityChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldHum.getText(), typeX, "Humidity", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			/*try {
				if(checkBoxLum.isSelected()) {
					if(typeX.equals("Zeit"))
						charts.add(new LuminanceChartFactory(typeX, readData(textFieldLum.getText(), "Helligkeit", xMin, xMax, yMin, yMax)));
					else
						charts.add(new LuminanceChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldLum.getText(), typeX, "Helligkeit", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}*/

			try {
				if (checkBoxMag.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new MagneticFieldChartFactory(typeX, readData(textFieldMag.getText(), "Magnetic Flux Density X", xMin, xMax, yMin, yMax)));
					else
						charts.add(new MagneticFieldChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldMag.getText(), typeX, "Magnetic Flux Density X", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			try {
				if (checkBoxRad.isSelected()) {
					if (typeX.equals("Zeit"))
						charts.add(new RadiationChartFactory(typeX, readData(textFieldRad.getText(), "Irradiance", xMin, xMax, yMin, yMax)));
					else
						charts.add(new RadiationChartFactory(typeX, readData(getXAxisTextField().getText(), textFieldRad.getText(), typeX, "Irradiance", xMin, xMax, yMin, yMax)));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			if (radioButtonSingle.isSelected()) {

				MultiChartFactory factory;
				List<Map<Double, Float>> dataList = new ArrayList<>();
				List<String> dataTypes = new ArrayList<>();

				for (WeatherChartFactory chart : charts) {

					dataTypes.add(chart.typeY);
					dataList.add(chart.data);
				}

				factory = new MultiChartFactory(typeX, dataTypes, dataList);
				charts.clear();
				charts.add(factory);
			}

			new ChartPopup(charts);
			charts.clear();
		});

		buttonChooseHgt.addActionListener(e -> chooseFile(textFieldHgt));
		buttonChooseTemp.addActionListener(e -> chooseFile(textFieldTemp));
		buttonChoosePress.addActionListener(e -> chooseFile(textFieldPress));
		buttonChooseAcc.addActionListener(e -> chooseFile(textFieldAcc));
		buttonChooseGyro.addActionListener(e -> chooseFile(textFieldGyro));
		buttonChooseHum.addActionListener(e -> chooseFile(textFieldHum));
		//buttonChooseLum.addActionListener(e -> chooseFile(textFieldLum));
		buttonChooseMag.addActionListener(e -> chooseFile(textFieldMag));
		buttonChooseRad.addActionListener(e -> chooseFile(textFieldRad));

		add(panel);
		pack();
		RefineryUtilities.centerFrameOnScreen(this);
		setVisible(true);
	}

	JTextField getXAxisTextField() {

		if (radioButtonHgt.isSelected())
			return textFieldHgt;

		if (radioButtonTemp.isSelected())
			return textFieldTemp;

		if (radioButtonPress.isSelected())
			return textFieldPress;

		if (radioButtonAcc.isSelected())
			return textFieldAcc;

		if (radioButtonGyro.isSelected())
			return textFieldGyro;

		if (radioButtonHum.isSelected())
			return textFieldHum;

		/*if(radioButtonLum.isSelected())
			return textFieldLum;*/

		if (radioButtonMag.isSelected())
			return textFieldMag;

		if (radioButtonRad.isSelected())
			return textFieldRad;

		return new JTextField();
	}

	/**
	 * Reads input data needed for a chart that displays the specified data against the time
	 *
	 * @param filepath Path to the file containing the chart data
	 * @return A map where the keys are timestamps and the values are raw data recorded at that time
	 * @throws IOException The file can not be found
	 */
	Map<Double, Float> readData(@NotNull String filepath, String type, double xMin, double xMax, float yMin, float yMax) throws IOException {

		Map<Double, Float> result = new TreeMap<>();

		BufferedReader inputReader = new BufferedReader(new FileReader(new File(filepath)));
		String[] inputData;
		String input;
		double x;
		float y;
		int dataColumn = -1;

		input = inputReader.readLine();
		inputData = input.split(";");
		for (int i = 0; i < inputData.length; i++) {
			if (inputData[i].contains(type))
				dataColumn = i;
		}

		if (dataColumn == -1)
			throw new IOException("Datensatz nicht gefunden");

		input = inputReader.readLine();
		while (input != null) {

			inputData = input.split(";");
			x = Double.parseDouble(inputData[0]);
			y = Float.parseFloat(inputData[dataColumn]);

			if (x >= xMin && x <= xMax && y >= yMin && y <= yMax)
				result.put(x, y);

			input = inputReader.readLine();
		}

		return result;
	}

	/**
	 * Reads input data needed for a chart that displays the specified data against some other specified data
	 *
	 * @param filepathX Path to the file containing the chart data for the X-Axis
	 * @param filepathY Path to the file containing the chart data for the Y-Axis
	 * @return A map where the keys and values are data recorded approximately at the same time
	 * @throws IOException The file can not be found
	 */
	Map<Double, Float> readData(@NotNull String filepathX, @NotNull String filepathY, String typeX, String typeY, double xMin, double xMax, float yMin, float yMax) throws IOException {

		Map<Double, Float> result = new TreeMap<>();

		BufferedReader inputReaderX = new BufferedReader(new FileReader(new File(filepathX)));
		BufferedReader inputReaderY = new BufferedReader(new FileReader(new File(filepathY)));
		String[] inputDataX, inputDataY;
		String inputX, inputY;
		double x;
		float y;
		int dataColumnX = -1, dataColumnY = -1;

		inputX = inputReaderX.readLine();
		inputDataX = inputX.split(";");
		for (int i = 0; i < inputDataX.length; i++) {
			if (inputDataX[i].contains(typeX))
				dataColumnX = i;
		}

		inputY = inputReaderY.readLine();
		inputDataY = inputY.split(";");
		for (int i = 0; i < inputDataY.length; i++) {
			if (inputDataY[i].contains(typeY))
				dataColumnY = i;
		}

		if (dataColumnX == -1 || dataColumnY == -1)
			throw new IOException("Datensatz nicht gefunden");

		inputX = inputReaderX.readLine();
		inputY = inputReaderY.readLine();
		while (inputX != null && inputY != null) {

			inputDataX = inputX.split(";");
			inputDataY = inputY.split(";");

			x = Double.parseDouble(inputDataX[dataColumnX]);
			y = Float.parseFloat(inputDataY[dataColumnY]);

			if (x >= xMin && x <= xMax && y >= yMin && y <= yMax)
				result.put(x, y);

			inputX = inputReaderX.readLine();
			inputY = inputReaderY.readLine();
		}

		return result;
	}

	void chooseFile(JTextField target) {

		JFileChooser chooser = new JFileChooser();
		int returnVal = chooser.showOpenDialog(getParent());

		if (returnVal == JFileChooser.APPROVE_OPTION) {

			String file = chooser.getSelectedFile().getAbsolutePath();

			target.setText(file);
			memory.put(associations.get(target).getActionCommand(), file);

			try {
				Files.writeString(Paths.get("inputFiles.json"), memory.toJSONString(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {

		EventQueue.invokeLater(Main::new);
	}

	{
// GUI initializer generated by IntelliJ IDEA GUI Designer
// >>> IMPORTANT!! <<<
// DO NOT EDIT OR ADD ANY CODE HERE!
		$$$setupUI$$$();
	}

	/**
	 * Method generated by IntelliJ IDEA GUI Designer
	 * >>> IMPORTANT!! <<<
	 * DO NOT edit this method OR call it in your code!
	 *
	 * @noinspection ALL
	 */
	private void $$$setupUI$$$() {
		panel = new JPanel();
		panel.setLayout(new GridBagLayout());
		checkBoxTemp = new JCheckBox();
		checkBoxTemp.setText("Temperatur");
		GridBagConstraints gbc;
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 8;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxTemp, gbc);
		checkBoxPress = new JCheckBox();
		checkBoxPress.setText("Luftdruck");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 10;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxPress, gbc);
		checkBoxAcc = new JCheckBox();
		checkBoxAcc.setText("Beschleunigung");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 12;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxAcc, gbc);
		checkBoxGyro = new JCheckBox();
		checkBoxGyro.setText("Ausrichtung");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 14;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxGyro, gbc);
		checkBoxHum = new JCheckBox();
		checkBoxHum.setText("Luftfeuchtigkeit");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 16;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxHum, gbc);
		checkBoxMag = new JCheckBox();
		checkBoxMag.setText("Magnetfeld");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 18;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxMag, gbc);
		checkBoxRad = new JCheckBox();
		checkBoxRad.setText("UV-Strahlung");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 20;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxRad, gbc);
		final JPanel spacer1 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 7;
		gbc.gridy = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(spacer1, gbc);
		final JLabel label1 = new JLabel();
		label1.setText("Bitte wählen sie, welche Graphen erstellt werden sollen ");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 1;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label1, gbc);
		final JPanel spacer2 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 0;
		gbc.gridy = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(spacer2, gbc);
		textFieldPress = new JTextField();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 10;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldPress, gbc);
		textFieldTemp = new JTextField();
		textFieldTemp.setInheritsPopupMenu(false);
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 8;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldTemp, gbc);
		textFieldRad = new JTextField();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 20;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldRad, gbc);
		final JLabel label2 = new JLabel();
		label2.setText("und wo sich die Dateien mit den entsprechenden Messdaten befinden");
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 1;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label2, gbc);
		textFieldMag = new JTextField();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 18;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldMag, gbc);
		textFieldHum = new JTextField();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 16;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldHum, gbc);
		textFieldAcc = new JTextField();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 12;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldAcc, gbc);
		final JPanel spacer3 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 21;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer3, gbc);
		final JPanel spacer4 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 0;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer4, gbc);
		buttonChooseTemp = new JButton();
		buttonChooseTemp.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 8;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChooseTemp, gbc);
		buttonChoosePress = new JButton();
		buttonChoosePress.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 10;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChoosePress, gbc);
		buttonChooseAcc = new JButton();
		buttonChooseAcc.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 12;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChooseAcc, gbc);
		buttonChooseGyro = new JButton();
		buttonChooseGyro.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 14;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChooseGyro, gbc);
		buttonChooseHum = new JButton();
		buttonChooseHum.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 16;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChooseHum, gbc);
		buttonChooseMag = new JButton();
		buttonChooseMag.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 18;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChooseMag, gbc);
		buttonChooseRad = new JButton();
		buttonChooseRad.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 20;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChooseRad, gbc);
		final JPanel spacer5 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 5;
		gbc.gridy = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(spacer5, gbc);
		checkBoxHgt = new JCheckBox();
		checkBoxHgt.setText("Höhe");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 6;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(checkBoxHgt, gbc);
		textFieldHgt = new JTextField();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 6;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldHgt, gbc);
		buttonChooseHgt = new JButton();
		buttonChooseHgt.setText("Datei wählen");
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 6;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(buttonChooseHgt, gbc);
		final JPanel spacer6 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 2;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer6, gbc);
		radioButtonMulti = new JRadioButton();
		radioButtonMulti.setSelected(true);
		radioButtonMulti.setText("Mehrere Diagramme");
		radioButtonMulti.setToolTipText("Stellt jeden gewählten Datensatz in einem eigenen Diagramm da");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 4;
		panel.add(radioButtonMulti, gbc);
		radioButtonSingle = new JRadioButton();
		radioButtonSingle.setText("Einzelnes Diagramm");
		radioButtonSingle.setToolTipText("Stellt alle gewählten Datensätze in einem gemeinsamen Diagramm da");
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 4;
		panel.add(radioButtonSingle, gbc);
		final JPanel spacer7 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 5;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer7, gbc);
		final JLabel label3 = new JLabel();
		label3.setText("X-Achse");
		label3.setToolTipText("Der hier gewählte Wert wird auf der X-Achse aller erstellten Diagramme abgebildet.");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 5;
		panel.add(label3, gbc);
		final JLabel label4 = new JLabel();
		label4.setText("Y-Achse");
		label4.setToolTipText("Die hier gewählten Werte werden auf der Y-Achse der erstellten Diagramme abgebildet");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 5;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label4, gbc);
		final JLabel label5 = new JLabel();
		label5.setText("Zeit (Standard)");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 22;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label5, gbc);
		radioButtonHgt = new JRadioButton();
		radioButtonHgt.setActionCommand("Altitude");
		radioButtonHgt.setName("");
		radioButtonHgt.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 6;
		panel.add(radioButtonHgt, gbc);
		radioButtonTemp = new JRadioButton();
		radioButtonTemp.setActionCommand("Temperature");
		radioButtonTemp.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 8;
		panel.add(radioButtonTemp, gbc);
		radioButtonPress = new JRadioButton();
		radioButtonPress.setActionCommand("Pressure");
		radioButtonPress.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 10;
		panel.add(radioButtonPress, gbc);
		radioButtonAcc = new JRadioButton();
		radioButtonAcc.setActionCommand("Acceleration Z");
		radioButtonAcc.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 12;
		panel.add(radioButtonAcc, gbc);
		radioButtonGyro = new JRadioButton();
		radioButtonGyro.setActionCommand("Rotation Z");
		radioButtonGyro.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 14;
		panel.add(radioButtonGyro, gbc);
		radioButtonHum = new JRadioButton();
		radioButtonHum.setActionCommand("Humidity");
		radioButtonHum.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 16;
		panel.add(radioButtonHum, gbc);
		radioButtonMag = new JRadioButton();
		radioButtonMag.setActionCommand("Magnetic Flux Density X");
		radioButtonMag.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 18;
		panel.add(radioButtonMag, gbc);
		radioButtonRad = new JRadioButton();
		radioButtonRad.setActionCommand("Irradiance");
		radioButtonRad.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 20;
		panel.add(radioButtonRad, gbc);
		radioButtonTime = new JRadioButton();
		radioButtonTime.setActionCommand("Zeit");
		radioButtonTime.setSelected(true);
		radioButtonTime.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 22;
		panel.add(radioButtonTime, gbc);
		textFieldGyro = new JTextField();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 14;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldGyro, gbc);
		final JLabel label6 = new JLabel();
		label6.setText("X - Minimum");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 28;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label6, gbc);
		final JPanel spacer8 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 7;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer8, gbc);
		final JPanel spacer9 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 9;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer9, gbc);
		final JPanel spacer10 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 11;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer10, gbc);
		final JPanel spacer11 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 13;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer11, gbc);
		final JPanel spacer12 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 15;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer12, gbc);
		final JPanel spacer13 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 17;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer13, gbc);
		final JPanel spacer14 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 19;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer14, gbc);
		final JPanel spacer15 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 2;
		gbc.gridy = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(spacer15, gbc);
		final JLabel label7 = new JLabel();
		label7.setText("X - Maximum");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 29;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label7, gbc);
		buttonCreateCharts = new JButton();
		buttonCreateCharts.setText("Diagramme erstellen");
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 35;
		gbc.weightx = 1.0;
		panel.add(buttonCreateCharts, gbc);
		final JPanel spacer16 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 30;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer16, gbc);
		final JLabel label8 = new JLabel();
		label8.setText("Y - Minimum");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 31;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label8, gbc);
		final JLabel label9 = new JLabel();
		label9.setText("Y - Maximum");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 32;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label9, gbc);
		final JPanel spacer17 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 33;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer17, gbc);
		textFieldXMin = new JTextField();
		textFieldXMin.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 28;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldXMin, gbc);
		textFieldXMax = new JTextField();
		textFieldXMax.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 29;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldXMax, gbc);
		textFieldYMin = new JTextField();
		textFieldYMin.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 31;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldYMin, gbc);
		textFieldYMax = new JTextField();
		textFieldYMax.setText("");
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 32;
		gbc.anchor = GridBagConstraints.WEST;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		panel.add(textFieldYMax, gbc);
		final JPanel spacer18 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 36;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer18, gbc);
		final JSeparator separator1 = new JSeparator();
		gbc = new GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = 24;
		gbc.fill = GridBagConstraints.BOTH;
		panel.add(separator1, gbc);
		final JSeparator separator2 = new JSeparator();
		gbc = new GridBagConstraints();
		gbc.gridx = 2;
		gbc.gridy = 24;
		gbc.fill = GridBagConstraints.BOTH;
		panel.add(separator2, gbc);
		final JSeparator separator3 = new JSeparator();
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 24;
		gbc.fill = GridBagConstraints.BOTH;
		panel.add(separator3, gbc);
		final JSeparator separator4 = new JSeparator();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 24;
		gbc.fill = GridBagConstraints.BOTH;
		panel.add(separator4, gbc);
		final JSeparator separator5 = new JSeparator();
		gbc = new GridBagConstraints();
		gbc.gridx = 6;
		gbc.gridy = 24;
		gbc.fill = GridBagConstraints.BOTH;
		panel.add(separator5, gbc);
		final JPanel spacer19 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 23;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer19, gbc);
		final JPanel spacer20 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 25;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer20, gbc);
		final JSeparator separator6 = new JSeparator();
		gbc = new GridBagConstraints();
		gbc.gridx = 5;
		gbc.gridy = 24;
		gbc.fill = GridBagConstraints.BOTH;
		panel.add(separator6, gbc);
		final JPanel spacer21 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 34;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer21, gbc);
		final JPanel spacer22 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 3;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer22, gbc);
		final JLabel label10 = new JLabel();
		label10.setText("Grenzwerte (optional):");
		gbc = new GridBagConstraints();
		gbc.gridx = 3;
		gbc.gridy = 26;
		gbc.anchor = GridBagConstraints.WEST;
		panel.add(label10, gbc);
		final JPanel spacer23 = new JPanel();
		gbc = new GridBagConstraints();
		gbc.gridx = 4;
		gbc.gridy = 27;
		gbc.fill = GridBagConstraints.VERTICAL;
		panel.add(spacer23, gbc);
	}

	/**
	 * @noinspection ALL
	 */
	public JComponent $$$getRootComponent$$$() {
		return panel;
	}

}
WeatherChartFactory.java
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.data.xy.XYDataset;

import java.util.*;

public abstract class WeatherChartFactory {

	public Map<Double, Float> data;
	public String typeX, typeY;

	public WeatherChartFactory(String typeX, String typeY, Map<Double, Float> data) {

		this.data = data;
		this.typeX = typeX;
		this.typeY = typeY;
	}

	public WeatherChartFactory(String typeX) {

		this(typeX, "Wetterdaten", new HashMap<>());
	}

	public XYDataset createDataset() {

		DefaultXYDataset result = new DefaultXYDataset();
		double[][] series = new double[2][data.size()];
		int i = 0;

		for(var entry: data.entrySet()) {

			if(typeX.equals("Zeit"))
				series[0][i] = entry.getKey() * 1000;
			else
				series[0][i] = entry.getKey();

			series[1][i] = entry.getValue();
			i++;
		}

		result.addSeries("weatherdata", series);
		return result;
	}

	public JFreeChart makeChart() {

		JFreeChart result = ChartFactory.createXYLineChart(typeY, "", "",
				createDataset(), PlotOrientation.VERTICAL, false, true, true);

		if(typeX.equals("Zeit"))
			result.getXYPlot().setDomainAxis(new DateAxis());

		return result;
	}
}
MultiChartFactory.java
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.data.xy.XYDataset;

import java.util.List;
import java.util.Map;

public class MultiChartFactory extends WeatherChartFactory {

	List<Map<Double, Float>> dataList;
	List<String> dataTypes;

	public MultiChartFactory(String typeX, List<String> dataTypes, List<Map<Double, Float>> dataList) {

		super(typeX);

		this.dataTypes = dataTypes;
		this.dataList = dataList;
	}

	@Override
	public XYDataset createDataset() {

		DefaultXYDataset result = new DefaultXYDataset();
		double[][] series;
		int j;

		for(int i = 0; i < dataList.size(); i++) {

			series = new double[2][dataList.get(i).size()];
			j = 0;

			for(var entry: dataList.get(i).entrySet()) {

				if(typeX.equals("Zeit"))
					series[0][j] = entry.getKey() * 1000;
				else
					series[0][j] = entry.getKey();

				series[1][j] = entry.getValue();
				j++;
			}

			result.addSeries(dataTypes.get(i), series);
		}

		return result;
	}

	@Override
	public JFreeChart makeChart() {

		JFreeChart result = ChartFactory.createXYLineChart(typeY, "", "",
				createDataset(), PlotOrientation.VERTICAL, true, true, true);

		if(typeX.equals("Zeit"))
			result.getXYPlot().setDomainAxis(new DateAxis());

		return result;
	}
}
ChartPopup.java
import Factories.WeatherChartFactory;
import org.jetbrains.annotations.NotNull;
import org.jfree.chart.ChartPanel;
import org.jfree.ui.RefineryUtilities;

import javax.swing.*;
import java.awt.*;
import java.util.List;

public class ChartPopup extends JDialog {

	private JPanel contentPane;
	private JPanel chartPanel;
	private JButton buttonOK;

	// TODO Besseres Layout
	public ChartPopup(@NotNull List<WeatherChartFactory> charts) {

		setContentPane(contentPane);
		setModal(true);
		getRootPane().setDefaultButton(buttonOK);

		buttonOK.addActionListener(e -> onOK());

		double averageRowsAndColumns = Math.sqrt(charts.size());
		chartPanel.setLayout(new GridLayout((int) Math.floor(averageRowsAndColumns), (int) Math.ceil(averageRowsAndColumns)));

		ChartPanel next;
		for (var chart : charts) {

			next = new ChartPanel(chart.makeChart());
			next.setPreferredSize(new Dimension(612, 378));
			chartPanel.add(next);
		}

		pack();
		RefineryUtilities.centerFrameOnScreen(this);
		setVisible(true);
	}

	private void onOK() {

		dispose();
	}

	{
// GUI initializer generated by IntelliJ IDEA GUI Designer
// >>> IMPORTANT!! <<<
// DO NOT EDIT OR ADD ANY CODE HERE!
		$$$setupUI$$$();
	}

	/**
	 * Method generated by IntelliJ IDEA GUI Designer
	 * >>> IMPORTANT!! <<<
	 * DO NOT edit this method OR call it in your code!
	 *
	 * @noinspection ALL
	 */
	private void $$$setupUI$$$() {
		contentPane = new JPanel();
		contentPane.setLayout(new com.intellij.uiDesigner.core.GridLayoutManager(2, 1, new Insets(10, 10, 10, 10), -1, -1));
		final JPanel panel1 = new JPanel();
		panel1.setLayout(new com.intellij.uiDesigner.core.GridLayoutManager(1, 3, new Insets(0, 0, 0, 0), -1, -1));
		contentPane.add(panel1, new com.intellij.uiDesigner.core.GridConstraints(1, 0, 1, 1, com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER, com.intellij.uiDesigner.core.GridConstraints.FILL_BOTH, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK | com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW, 1, null, null, null, 0, false));
		final JPanel panel2 = new JPanel();
		panel2.setLayout(new com.intellij.uiDesigner.core.GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
		panel1.add(panel2, new com.intellij.uiDesigner.core.GridConstraints(0, 1, 1, 1, com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER, com.intellij.uiDesigner.core.GridConstraints.FILL_BOTH, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK | com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK | com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
		buttonOK = new JButton();
		buttonOK.setText("Schließen");
		panel2.add(buttonOK, new com.intellij.uiDesigner.core.GridConstraints(0, 0, 1, 1, com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER, com.intellij.uiDesigner.core.GridConstraints.FILL_HORIZONTAL, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK | com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
		final com.intellij.uiDesigner.core.Spacer spacer1 = new com.intellij.uiDesigner.core.Spacer();
		panel1.add(spacer1, new com.intellij.uiDesigner.core.GridConstraints(0, 0, 1, 1, com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER, com.intellij.uiDesigner.core.GridConstraints.FILL_HORIZONTAL, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
		final com.intellij.uiDesigner.core.Spacer spacer2 = new com.intellij.uiDesigner.core.Spacer();
		panel1.add(spacer2, new com.intellij.uiDesigner.core.GridConstraints(0, 2, 1, 1, com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER, com.intellij.uiDesigner.core.GridConstraints.FILL_HORIZONTAL, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
		chartPanel = new JPanel();
		chartPanel.setLayout(new com.intellij.uiDesigner.core.GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
		contentPane.add(chartPanel, new com.intellij.uiDesigner.core.GridConstraints(0, 0, 1, 1, com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER, com.intellij.uiDesigner.core.GridConstraints.FILL_BOTH, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK | com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW, com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK | com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
	}

	/**
	 * @noinspection ALL
	 */
	public JComponent $$$getRootComponent$$$() {
		return contentPane;
	}
}


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.

Auswertung.java
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);
	}
}