Skip to content

Commit

Permalink
Include GPS location in captured JPEG image EXIF data. Add a simple a…
Browse files Browse the repository at this point in the history
…lgorithm to detect the phase of balloon flight and report it in telemetry data. Provide information about last transmitted/received telemetry in a new status API endpoint. Add watchdog scripts that poll the status endpoint and restart ertnode if there is no activity for a certain time.
  • Loading branch information
mikaelnousiainen committed Jul 7, 2017
1 parent 577f962 commit 1f544f0
Show file tree
Hide file tree
Showing 35 changed files with 1,200 additions and 128 deletions.
19 changes: 10 additions & 9 deletions ertgateway/ertgateway-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@
#include "ertgateway-common.h"
#include "ertgateway-server.h"

static void ert_gateway_server_node_telemetry_listener(char *event, void *data, void *context)
static void ert_gateway_server_node_telemetry_collected_listener(char *event, void *data, void *context)
{
ert_server *server = (ert_server *) context;
ert_server_config *server_config = ert_server_get_config(server);
ert_data_logger_entry *entry = (ert_data_logger_entry *) data;

ert_server_update_data_logger_entry_node(server, server_config->data_logger_entry_serializer, entry);
ert_server_update_data_logger_entry_received(server, entry);
}

static void ert_gateway_server_gateway_telemetry_listener(char *event, void *data, void *context)
static void ert_gateway_server_gateway_telemetry_collected_listener(char *event, void *data, void *context)
{
ert_server *server = (ert_server *) context;
ert_server_config *server_config = ert_server_get_config(server);
Expand All @@ -30,7 +31,7 @@ static void ert_gateway_server_gateway_telemetry_listener(char *event, void *dat
ert_server_update_data_logger_entry_gateway(server, server_config->data_logger_entry_serializer, entry);
}

static void ert_gateway_server_node_image_listener(char *event, void *data, void *context)
static void ert_gateway_server_node_image_received_listener(char *event, void *data, void *context)
{
ert_server *server = (ert_server *) context;
ert_image_metadata *metadata = (ert_image_metadata *) data;
Expand All @@ -41,21 +42,21 @@ static void ert_gateway_server_node_image_listener(char *event, void *data, void
void ert_gateway_server_attach_events(ert_event_emitter *event_emitter, ert_server *server)
{
ert_event_emitter_add_listener(event_emitter, ERT_EVENT_NODE_TELEMETRY_RECEIVED,
ert_gateway_server_node_telemetry_listener, server);
ert_gateway_server_node_telemetry_collected_listener, server);
ert_event_emitter_add_listener(event_emitter, ERT_EVENT_GATEWAY_TELEMETRY_RECEIVED,
ert_gateway_server_gateway_telemetry_listener, server);
ert_gateway_server_gateway_telemetry_collected_listener, server);

ert_event_emitter_add_listener(event_emitter, ERT_EVENT_NODE_IMAGE_RECEIVED,
ert_gateway_server_node_image_listener, server);
ert_gateway_server_node_image_received_listener, server);
}

void ert_gateway_server_detach_events(ert_event_emitter *event_emitter)
{
ert_event_emitter_remove_listener(event_emitter, ERT_EVENT_NODE_TELEMETRY_RECEIVED,
ert_gateway_server_node_telemetry_listener);
ert_gateway_server_node_telemetry_collected_listener);
ert_event_emitter_remove_listener(event_emitter, ERT_EVENT_GATEWAY_TELEMETRY_RECEIVED,
ert_gateway_server_gateway_telemetry_listener);
ert_gateway_server_gateway_telemetry_collected_listener);

ert_event_emitter_remove_listener(event_emitter, ERT_EVENT_NODE_IMAGE_RECEIVED,
ert_gateway_server_node_image_listener);
ert_gateway_server_node_image_received_listener);
}
2 changes: 1 addition & 1 deletion ertgateway/ertgateway-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export ERT_LOG_PATH="${SCRIPT_PATH}/log/"
mkdir -p "${ERT_LOG_PATH}"

# Run one cycle of hardware initialization to have it in a sane state
"${SCRIPT_PATH}/ertgateway" -i
"${SCRIPT_PATH}/ertgateway" --init-only

nohup bash -c "while true; do ERT_LOG_PATH=\"${ERT_LOG_PATH}\" \"${SCRIPT_PATH}/ertgateway\" 1>> \"${ERT_LOG_PATH}ertgateway-stdout.log\" 2>> \"${ERT_LOG_PATH}ertgateway-stderr.log\"; sleep 5; done" &
2 changes: 2 additions & 0 deletions ertnode/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ configure_file(${PROJECT_SOURCE_DIR}/zlog.conf ${PROJECT_BINARY_DIR} COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/ertnode-start.sh ${PROJECT_BINARY_DIR} COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/ertnode-prepare-raspbian.sh ${PROJECT_BINARY_DIR} COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/ertnode-start-dev.sh ${PROJECT_BINARY_DIR} COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/ertnode-check.sh ${PROJECT_BINARY_DIR} COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/ertnode-watch.sh ${PROJECT_BINARY_DIR} COPYONLY)

IF (NOT EXISTS "${PROJECT_BINARY_DIR}/ertnode.yaml")
configure_file(${PROJECT_SOURCE_DIR}/ertnode.yaml ${PROJECT_BINARY_DIR} COPYONLY)
Expand Down
2 changes: 1 addition & 1 deletion ertnode/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Install library and tool dependencies:
----
apt-get install cmake make gcc git
apt-get install ntp gpsd libgps21 libgps-dev libyaml-0-2 libyaml-dev
apt-get install libraspberrypi-bin webp imagemagick
apt-get install libraspberrypi-bin webp imagemagick jq
----

=== Raspberry Pi configuration
Expand Down
148 changes: 148 additions & 0 deletions ertnode/ertnode-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/bin/bash

# Allow commands to fail
set +e

MAXIMUM_TELEMETRY_TIMESTAMP_AGE_SECONDS=120
MAXIMUM_TELEMETRY_TRANSFER_FAILURE_COUNT=3

APP_SERVER="localhost:9000"
APP_STATUS_URL="http://${APP_SERVER}/api/status"
APP_EXECUTABLE_NAME="ertnode"

function get_app_pid()
{
APP_PID=`ps a | grep -E "/${APP_EXECUTABLE_NAME}$" | sed -r "s/[^0-9]*([0-9]+).*/\1/"`
if [ -z "${APP_PID}" ]; then
echo "Error finding app PID for executable: ${APP_EXECUTABLE_NAME}"
return 1
fi

echo ${APP_PID}

return 0
}

function restart_app_gracefully()
{
APP_PID=`get_app_pid`
RESULT=$?
if [ "${RESULT}" -ne "0" ]; then
return 1
fi

echo "`date "+%Y-%m-%d %H:%M:%S"` Sending SIGTERM to PID ${APP_PID} ..."
kill -TERM ${APP_PID}

WAIT_COUNT=0
while [ "${WAIT_COUNT}" -lt 25 ]
do
kill -0 ${APP_PID}
RESULT=$?
if [ "${RESULT}" -ne "0" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` PID ${APP_PID} terminated gracefully"
return 0
fi

WAIT_COUNT=$((${WAIT_COUNT} + 1))
echo "`date "+%Y-%m-%d %H:%M:%S"` Waiting for PID ${APP_PID} to terminate (${WAIT_COUNT}) ..."
sleep 1
done

echo "`date "+%Y-%m-%d %H:%M:%S"` Process not terminating, sending SIGKILL to PID ${APP_PID} ..."
kill -KILL ${APP_PID}
echo "`date "+%Y-%m-%d %H:%M:%S"` PID ${APP_PID} killed"

return 0
}

function restart_app_force()
{
APP_PID=`get_app_pid`
RESULT=$?
if [ "${RESULT}" -ne "0" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Error: ${APP_PID}"
return 1
fi

echo "`date "+%Y-%m-%d %H:%M:%S"` Sending SIGKILL to PID ${APP_PID} ..."
kill -KILL ${APP_PID}

return 0
}

echo "`date "+%Y-%m-%d %H:%M:%S"` Checking ERT status: ${APP_STATUS_URL} ..."

APP_STATUS_OUTPUT=`curl -s "${APP_STATUS_URL}"`
RESULT=$?
if [ "${RESULT}" -ne "0" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Error checking app status in URL ${APP_STATUS_URL} -- curl exit code ${RESULT}"
restart_app_force
exit 1
fi

LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_MILLIS=`echo "${APP_STATUS_OUTPUT}" | jq ".telemetry_transmitted.last_transferred_telemetry_entry_timestamp_millis"`
LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_SECONDS=$((${LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_MILLIS} / 1000))
CURRENT_TIMESTAMP_SECONDS=`date +%s`

if [ -z "${LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_MILLIS}" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Error getting last transferred telemetry timestamp ..."
restart_app_force
exit 1
fi
if [ -z "${LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_SECONDS}" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Error getting last transferred telemetry timestamp ..."
restart_app_force
exit 1
fi
if [ -z "${CURRENT_TIMESTAMP_SECONDS}" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Error getting current time"
restart_app_force
exit 1
fi

echo "`date "+%Y-%m-%d %H:%M:%S"` Last transferred telemetry timestamp: ${LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_SECONDS} seconds"
echo "`date "+%Y-%m-%d %H:%M:%S"` Current timestamp: ${CURRENT_TIMESTAMP_SECONDS} seconds"

if [ "${LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_SECONDS}" -eq "0" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` No telemetry transferred yet"
exit 0
fi

LAST_TRANSFERRED_TELEMETRY_AGE_SECONDS=$((${CURRENT_TIMESTAMP_SECONDS} - ${LAST_TRANSFERRED_TELEMETRY_TIMESTAMP_SECONDS}))

if [ -z "${LAST_TRANSFERRED_TELEMETRY_AGE_SECONDS}" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Error calculating last transferred telemetry age"
restart_app_force
exit 1
fi

echo "`date "+%Y-%m-%d %H:%M:%S"` Last transferred telemetry age: ${LAST_TRANSFERRED_TELEMETRY_AGE_SECONDS} seconds"

if [ "${LAST_TRANSFERRED_TELEMETRY_AGE_SECONDS}" -gt "${MAXIMUM_TELEMETRY_TIMESTAMP_AGE_SECONDS}" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Last transferred telemetry older than ${MAXIMUM_TELEMETRY_TIMESTAMP_AGE_SECONDS} seconds, restarting app ..."
restart_app_gracefully
exit 0
fi

echo "`date "+%Y-%m-%d %H:%M:%S"` Last transferred telemetry newer than ${MAXIMUM_TELEMETRY_TIMESTAMP_AGE_SECONDS} seconds -- OK"

TELEMETRY_TRANSFER_FAILURE_COUNT=`echo "${APP_STATUS_OUTPUT}" | jq ".telemetry_transmitted.transfer_failure_count"`

if [ -z "${TELEMETRY_TRANSFER_FAILURE_COUNT}" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Error getting transfer failure count ..."
restart_app_force
exit 1
fi

echo "`date "+%Y-%m-%d %H:%M:%S"` Telemetry transfer failure count: ${TELEMETRY_TRANSFER_FAILURE_COUNT} seconds"

if [ "${TELEMETRY_TRANSFER_FAILURE_COUNT}" -gt "${MAXIMUM_TELEMETRY_TRANSFER_FAILURE_COUNT}" ]; then
echo "`date "+%Y-%m-%d %H:%M:%S"` Telemetry transfer failure count higher than ${MAXIMUM_TELEMETRY_TRANSFER_FAILURE_COUNT}, restarting app ..."
restart_app_gracefully
exit 0
fi

echo "`date "+%Y-%m-%d %H:%M:%S"` Telemetry transfer failure count less than ${MAXIMUM_TELEMETRY_TRANSFER_FAILURE_COUNT} -- OK"

exit 0
51 changes: 50 additions & 1 deletion ertnode/ertnode-collector-image.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,41 @@
#include "ertnode-image.h"
#include "ertnode-collector-image.h"

typedef struct _ert_node_image_collector_context {
ert_node *node;

pthread_mutex_t current_data_mutex;
volatile bool current_data_valid;
ert_gps_data current_gps_data;
} ert_node_image_collector_context;

static void ert_node_image_collector_telemetry_collected_listener(char *event, void *data, void *context) {
ert_node_image_collector_context *image_collector_context = (ert_node_image_collector_context *) context;
ert_data_logger_entry *entry = (ert_data_logger_entry *) data;

pthread_mutex_lock(&image_collector_context->current_data_mutex);
memcpy(&image_collector_context->current_gps_data, &entry->params->gps_data, sizeof(ert_gps_data));
image_collector_context->current_data_valid = true;
pthread_mutex_unlock(&image_collector_context->current_data_mutex);
}

void *ert_node_image_collector(void *context)
{
ert_node *node = (ert_node *) context;
int result;

ert_node_image_collector_context image_collector_context = {0};
image_collector_context.node = node;

result = pthread_mutex_init(&image_collector_context.current_data_mutex, NULL);
if (result != 0) {
ert_log_fatal("Error initializing current data mutex, result %d", result);
return NULL;
}

ert_event_emitter_add_listener(node->event_emitter, ERT_EVENT_NODE_TELEMETRY_COLLECTED,
ert_node_image_collector_telemetry_collected_listener, &image_collector_context);

ert_log_info("Image collector thread running");

char image_path[PATH_MAX];
Expand All @@ -37,6 +67,9 @@ void *ert_node_image_collector(void *context)
char transmitted_image_filename[PATH_MAX];
char transmitted_image_full_path_filename[PATH_MAX];

ert_process_sleep_with_interrupt(
node->config.sender_telemetry_config.telemetry_collect_interval_seconds * 2, &node->running);

while (node->running) {
struct timespec image_timestamp;

Expand Down Expand Up @@ -66,12 +99,23 @@ void *ert_node_image_collector(void *context)

ert_log_info("Capturing image %d ...", image_index);

ert_gps_data gps_data;
ert_gps_data *gps_data_pointer = NULL;

pthread_mutex_lock(&image_collector_context.current_data_mutex);
if (image_collector_context.current_data_valid) {
memcpy(&gps_data, &image_collector_context.current_gps_data, sizeof(ert_gps_data));
gps_data_pointer = &gps_data;
}
pthread_mutex_unlock(&image_collector_context.current_data_mutex);

result = ert_node_capture_image(
node->config.sender_image_config.raspistill_command,
original_image_full_path_filename,
node->config.sender_image_config.original_image_quality,
node->config.sender_image_config.horizontal_flip,
node->config.sender_image_config.vertical_flip);
node->config.sender_image_config.vertical_flip,
gps_data_pointer);
if (result < 0) {
ert_log_error("Error capturing image, result %d", result);
goto error;
Expand Down Expand Up @@ -118,7 +162,12 @@ void *ert_node_image_collector(void *context)
node->config.sender_image_config.image_capture_interval_seconds, &node->running);
}

ert_event_emitter_remove_listener(node->event_emitter, ERT_EVENT_NODE_TELEMETRY_COLLECTED,
ert_node_image_collector_telemetry_collected_listener);

ert_log_info("Image collector thread stopping");

pthread_mutex_destroy(&image_collector_context.current_data_mutex);

return NULL;
}
2 changes: 2 additions & 0 deletions ertnode/ertnode-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "ertapp-common.h"

#define ERT_EVENT_NODE_TELEMETRY_COLLECTED "node-telemetry-collected"
#define ERT_EVENT_NODE_TELEMETRY_TRANSMITTED "node-telemetry-transmitted"
#define ERT_EVENT_NODE_TELEMETRY_TRANSMISSION_FAILURE "node-telemetry-transmission-failure"

#define ERT_EVENT_NODE_IMAGE_CAPTURED "node-image-captured"

Expand Down
41 changes: 37 additions & 4 deletions ertnode/ertnode-image.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@
*/

#include <stdio.h>
#include <string.h>

#include "ertnode.h"
#include "ertnode-image.h"
#include "ert-exif.h"

int ert_node_capture_image(const char *command, const char *image_filename, int16_t quality, bool hflip, bool vflip)
int ert_node_capture_image(const char *command, const char *image_filename,
int16_t quality, bool hflip, bool vflip, ert_gps_data *gps_data)
{
const char *args[64];
size_t args_length = 128;
char *args_alloc[args_length];
size_t args_alloc_index = 0;
const char *args[args_length];
size_t argument_index = 0;
size_t exif_argument_length = ERT_EXIF_ENTRY_TAG_LENGTH + ERT_EXIF_ENTRY_VALUE_LENGTH + 32;
char exif_argument[exif_argument_length];

args[argument_index++] = command;

Expand Down Expand Up @@ -52,14 +60,39 @@ int ert_node_capture_image(const char *command, const char *image_filename, int1
args[argument_index++] = quality_string;
}

// TODO: Set EXIF data and GPS coordinates using --exif
if (gps_data != NULL) {
ert_exif_entry *exif_entries;
int result = ert_exif_gps_create(gps_data, &exif_entries);
if (result == 0) {
for (size_t index = 0; strlen(exif_entries[index].tag) > 0; index++) {
ert_exif_entry *entry = &exif_entries[index];
snprintf(exif_argument, exif_argument_length, "GPS.%s=%s", entry->tag, entry->value);

args[argument_index++] = "--exif";

char *argument_copy = strdup(exif_argument);
args[argument_index++] = argument_copy;
args_alloc[args_alloc_index++] = argument_copy;
}

free(exif_entries);
} else {
ert_log_warn("Error creating EXIF tags for image %s, result %d", image_filename, result);
}
}

args[argument_index++] = "--output";
args[argument_index++] = image_filename;

args[argument_index] = NULL;

return ert_process_run_command(command, args);
int result = ert_process_run_command(command, args);

for (size_t index = 0; index < args_alloc_index; index++) {
free(args_alloc[index]);
}

return result;
}

int ert_node_resize_image(const char *command, const char *output_image_filename,
Expand Down
Loading

0 comments on commit 1f544f0

Please sign in to comment.