* Renamed plugin to teleshmem.
authorUrban Wallasch <urban.wallasch@freenet.de>
Sat, 27 Jul 2019 11:59:39 +0000 (13:59 +0200)
committerUrban Wallasch <urban.wallasch@freenet.de>
Sat, 27 Jul 2019 11:59:39 +0000 (13:59 +0200)
* (Re-)implemented logging to file in plugin.
* Reactivated telemetry_configuration() callback.

Makefile
telemetry.cpp [deleted file]
teleshmem.cpp [new file with mode: 0644]

index 1ae7701641fa90f91beae34e31729b5f4e96eeaa..260e29269e47f0389d605f4d3e2343867dc50fd7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@ SDK_INCLUDES=\
   -Isdk/include/eurotrucks2
 
 CFLAGS=-Wall -Wextra -std=c99 -DDEBUG -I.
+CPPFLAGS=-Wall -DLOGGING -I.
 
 UNAME:= $(shell uname -s)
 
@@ -23,14 +24,14 @@ else
   LIB_NAME_OPTION=-soname
 endif
 
-PLUGIN_SRC := telemetry.cpp shmget.c
+PLUGIN_SRC := teleshmem.cpp shmget.c
 
 .PHONY: all clean
 
-all: telehttpd telelogger telemetry.so
+all: telehttpd telelogger teleshmem.so
 
-telemetry.so: $(PLUGIN_SRC) $(SDK_HEADERS)
-       g++ -o $@ -fPIC -Wall --shared -Wl,$(LIB_NAME_OPTION),$@ $(SDK_INCLUDES) $(PLUGIN_SRC)
+teleshmem.so: $(PLUGIN_SRC) $(SDK_HEADERS)
+       g++ -o $@ $(CPPFLAGS) -fPIC --shared -Wl,$(LIB_NAME_OPTION),$@ $(SDK_INCLUDES) $(PLUGIN_SRC)
 
 telehttpd: telehttpd.o shmget.o net.o fserv.o
        $(CC) $(LDFLAGS) -o $@ -pthread $^
diff --git a/telemetry.cpp b/telemetry.cpp
deleted file mode 100644 (file)
index aa3802b..0000000
+++ /dev/null
@@ -1,465 +0,0 @@
-/**
- * @brief Simple logger.
- *
- * Writes the output into file inside the current directory.
- */
-
-// Windows stuff.
-
-#ifdef _WIN32
-#  define WINVER 0x0500
-#  define _WIN32_WINNT 0x0500
-#  include <windows.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <string.h>
-
-// SDK
-
-#include "scssdk_telemetry.h"
-#include "eurotrucks2/scssdk_eut2.h"
-#include "eurotrucks2/scssdk_telemetry_eut2.h"
-#include "amtrucks/scssdk_ats.h"
-#include "amtrucks/scssdk_telemetry_ats.h"
-
-//LINUX
-
-#include <unistd.h>
-#include <signal.h>
-
-#define UNUSED(x)
-
-/**
- * @brief Combined telemetry data and shared memory management.
- */
-
-#include "telemetry.h"
-#include "shmget.h"
-
-static struct telemetry_state_t *telemetry = NULL;
-
-static bool init_shm(void)
-{
-    if ( NULL == telemetry ) {
-        void *p;
-        p = init_shmput( TELE_SHM_KEY, sizeof *telemetry );
-        telemetry = static_cast<struct telemetry_state_t *>(p);
-    }
-    assert( NULL != telemetry );
-    return NULL != telemetry;
-}
-
-static void drop_shm(void)
-{
-    if ( NULL != telemetry ) {
-        release_shm( TELE_SHM_KEY, telemetry );
-        telemetry = NULL;
-    }
-}
-
-/**
- * @brief Last timestamp we received.
- */
-scs_timestamp_t last_timestamp = static_cast<scs_timestamp_t>(-1);
-
-
-/**
- * @brief Function writting message to the game internal log.
- */
-/*
-static void log_print(const char *const text, ...)
-{
-}
-
-static void log_line(const char *const text, ...)
-{
-}
-*/
-// Handling of individual events.
-
-SCSAPI_VOID telemetry_frame_start(const scs_event_t UNUSED(event), const void *const event_info, const scs_context_t UNUSED(context))
-{
-    const struct scs_telemetry_frame_start_t *const info = static_cast<const scs_telemetry_frame_start_t *>(event_info);
-
-    // The following processing of the timestamps is done so the output
-    // from this plugin has continuous time, it is not necessary otherwise.
-
-    // When we just initialized itself, assume that the time started
-    // just now.
-
-    if (last_timestamp == static_cast<scs_timestamp_t>(-1)) {
-        last_timestamp = info->paused_simulation_time;
-    }
-
-    // The timer might be sometimes restarted (e.g. after load) while
-    // we want to provide continuous time on our output.
-
-    if (info->flags & SCS_TELEMETRY_FRAME_START_FLAG_timer_restart) {
-        last_timestamp = 0;
-    }
-
-    // Advance the timestamp by delta since last frame.
-
-    telemetry->timestamp += (info->paused_simulation_time - last_timestamp);
-    last_timestamp = info->paused_simulation_time;
-
-    // The raw values.
-
-    telemetry->raw_rendering_timestamp = info->render_time;
-    telemetry->raw_simulation_timestamp = info->simulation_time;
-    telemetry->raw_paused_simulation_timestamp = info->paused_simulation_time;
-}
-
-SCSAPI_VOID telemetry_frame_end(const scs_event_t UNUSED(event), const void *const UNUSED(event_info), const scs_context_t UNUSED(context))
-{
-}
-
-SCSAPI_VOID telemetry_pause(const scs_event_t event, const void *const UNUSED(event_info), const scs_context_t UNUSED(context))
-{
-    telemetry->paused = (event == SCS_TELEMETRY_EVENT_paused);
-}
-
-SCSAPI_VOID telemetry_configuration(const scs_event_t event, const void *const event_info, const scs_context_t UNUSED(context))
-{
-#if 0
-    // Here we just print the configuration info.
-
-    const struct scs_telemetry_configuration_t *const info = static_cast<const scs_telemetry_configuration_t *>(event_info);
-    log_line("Configuration: %s", info->id);
-
-    for (const scs_named_value_t *current = info->attributes; current->name; ++current) {
-        log_print("  %s", current->name);
-        if (current->index != SCS_U32_NIL) {
-            log_print("[%u]", static_cast<unsigned>(current->index));
-        }
-        log_print(" : ");
-        switch (current->value.type) {
-            case SCS_VALUE_TYPE_INVALID: {
-                log_line("none");
-                break;
-            }
-            case SCS_VALUE_TYPE_bool: {
-                log_line("bool = %s", current->value.value_bool.value ? "true" : "false");
-                break;
-            }
-            case SCS_VALUE_TYPE_s32: {
-                log_line("s32 = %d", static_cast<int>(current->value.value_s32.value));
-                break;
-            }
-            case SCS_VALUE_TYPE_u32: {
-                log_line("u32 = %u", static_cast<unsigned>(current->value.value_u32.value));
-                break;
-            }
-            case SCS_VALUE_TYPE_u64: {
-                log_line("u64 = %" SCS_PF_U64, current->value.value_u64.value);
-                break;
-            }
-            case SCS_VALUE_TYPE_float: {
-                log_line("float = %f", current->value.value_float.value);
-                break;
-            }
-            case SCS_VALUE_TYPE_double: {
-                log_line("double = %f", current->value.value_double.value);
-                break;
-            }
-            case SCS_VALUE_TYPE_fvector: {
-                log_line(
-                    "fvector = (%f,%f,%f)",
-                    current->value.value_fvector.x,
-                    current->value.value_fvector.y,
-                    current->value.value_fvector.z
-                );
-                break;
-            }
-            case SCS_VALUE_TYPE_dvector: {
-                log_line(
-                    "dvector = (%f,%f,%f)",
-                    current->value.value_dvector.x,
-                    current->value.value_dvector.y,
-                    current->value.value_dvector.z
-                );
-                break;
-            }
-            case SCS_VALUE_TYPE_euler: {
-                log_line(
-                    "euler = h:%f p:%f r:%f",
-                    current->value.value_euler.heading * 360.0f,
-                    current->value.value_euler.pitch * 360.0f,
-                    current->value.value_euler.roll * 360.0f
-                );
-                break;
-            }
-            case SCS_VALUE_TYPE_fplacement: {
-                log_line(
-                    "fplacement = (%f,%f,%f) h:%f p:%f r:%f",
-                    current->value.value_fplacement.position.x,
-                    current->value.value_fplacement.position.y,
-                    current->value.value_fplacement.position.z,
-                    current->value.value_fplacement.orientation.heading * 360.0f,
-                    current->value.value_fplacement.orientation.pitch * 360.0f,
-                    current->value.value_fplacement.orientation.roll * 360.0f
-                );
-                break;
-            }
-            case SCS_VALUE_TYPE_dplacement: {
-                log_line(
-                    "dplacement = (%f,%f,%f) h:%f p:%f r:%f",
-                    current->value.value_dplacement.position.x,
-                    current->value.value_dplacement.position.y,
-                    current->value.value_dplacement.position.z,
-                    current->value.value_dplacement.orientation.heading * 360.0f,
-                    current->value.value_dplacement.orientation.pitch * 360.0f,
-                    current->value.value_dplacement.orientation.roll * 360.0f
-                );
-                break;
-            }
-            case SCS_VALUE_TYPE_string: {
-                log_line("string = %s", current->value.value_string.value);
-                break;
-            }
-            default: {
-                log_line("unknown");
-                break;
-            }
-        }
-    }
-
-    print_header = true;
-#endif
-}
-
-// Handling of individual channels.
-
-SCSAPI_VOID telemetry_store_dplacement(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
-{
-    assert(value);
-    assert(value->type == SCS_VALUE_TYPE_dplacement);
-    assert(context);
-    telemetry_state_t *const tele = static_cast<telemetry_state_t *>(context);
-
-    // This callback was registered with the SCS_TELEMETRY_CHANNEL_FLAG_no_value flag
-    // so it is called even when the value is not available.
-    if ( !value || value->type != SCS_VALUE_TYPE_dplacement ) {
-        tele->placement_available = false;
-        return;
-    }
-    tele->placement_available = true;
-    tele->x = value->value_dplacement.position.x;
-    tele->y = value->value_dplacement.position.y;
-    tele->z = value->value_dplacement.position.z;
-    tele->heading = value->value_dplacement.orientation.heading;
-    tele->pitch = value->value_dplacement.orientation.pitch;
-    tele->roll = value->value_dplacement.orientation.roll;
-}
-
-SCSAPI_VOID telemetry_store_float(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
-{
-    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
-    // so this callback is only called when a valid value is available.
-
-    assert(value);
-    assert(value->type == SCS_VALUE_TYPE_float);
-    assert(context);
-    *static_cast<float *>(context) = value->value_float.value;
-}
-
-SCSAPI_VOID telemetry_store_float_nz(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
-{
-    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
-    // so this callback is only called when a valid value is available.
-
-    assert(value);
-    assert(value->type == SCS_VALUE_TYPE_float);
-    assert(context);
-    if (value->value_float.value)
-        *static_cast<float *>(context) = value->value_float.value;
-}
-
-SCSAPI_VOID telemetry_store_s32(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
-{
-    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
-    // so this callback is only called when a valid value is available.
-
-    assert(value);
-    assert(value->type == SCS_VALUE_TYPE_s32);
-    assert(context);
-    *static_cast<int *>(context) = value->value_s32.value;
-}
-
-SCSAPI_VOID telemetry_store_bool(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
-{
-    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
-    // so this callback is only called when a valid value is available.
-
-    assert(value);
-    assert(value->type == SCS_VALUE_TYPE_bool);
-    assert(context);
-    *static_cast<bool *>(context) = value->value_bool.value;
-}
-
-/**
- * @brief Telemetry API initialization function.
- *
- * See scssdk_telemetry.h
- */
-SCSAPI_RESULT scs_telemetry_init(const scs_u32_t version, const scs_telemetry_init_params_t *const params)
-{
-    // We currently support only one version.
-
-    if (version != SCS_TELEMETRY_VERSION_1_00) {
-        return SCS_RESULT_unsupported;
-    }
-
-    const scs_telemetry_init_params_v100_t *const version_params = static_cast<const scs_telemetry_init_params_v100_t *>(params);
-    if ( !init_shm() ) {
-        version_params->common.log(SCS_LOG_TYPE_error, "Unable to initialize shared memory");
-        return SCS_RESULT_generic_error;
-    }
-
-    // Check application version. Note that this example uses fairly basic channels which are likely to be supported
-    // by any future SCS trucking game however more advanced application might want to at least warn the user if there
-    // is game or version they do not support.
-    snprintf( telemetry->game_id, sizeof telemetry->game_id, "%s", version_params->common.game_id );
-    telemetry->game_major_ver = SCS_GET_MAJOR_VERSION(version_params->common.game_version);
-    telemetry->game_minor_ver = SCS_GET_MINOR_VERSION(version_params->common.game_version);
-
-    if (strcmp(version_params->common.game_id, SCS_GAME_ID_EUT2) == 0) {
-
-        // Below the minimum version there might be some missing features (only minor change) or
-        // incompatible values (major change).
-
-        const scs_u32_t MINIMAL_VERSION = SCS_TELEMETRY_EUT2_GAME_VERSION_1_00;
-        if (version_params->common.game_version < MINIMAL_VERSION) {
-            //log_line("WARNING: Too old version of the game, some features might behave incorrectly");
-            telemetry->game_ver_warn = true;
-        }
-
-        // Future versions are fine as long the major version is not changed.
-
-        const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_EUT2_GAME_VERSION_CURRENT;
-        if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
-            //log_line("WARNING: Too new major version of the game, some features might behave incorrectly");
-            telemetry->game_ver_warn = true;
-        }
-    }
-    else if (strcmp(version_params->common.game_id, SCS_GAME_ID_ATS) == 0) {
-
-        // Bellow the minimum version there might be some missing features (only minor change) or
-        // incompatible values (major change).
-
-        const scs_u32_t MINIMAL_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_1_00;
-        if (version_params->common.game_version < MINIMAL_VERSION) {
-            //log_line("WARNING: Too old version of the game, some features might behave incorrectly");
-            telemetry->game_ver_warn = true;
-        }
-
-        // Future versions are fine as long the major version is not changed.
-
-        const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_CURRENT;
-        if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
-            //log_line("WARNING: Too new major version of the game, some features might behave incorrectly");
-            telemetry->game_ver_warn = true;
-        }
-    }
-    else {
-        //log_line("WARNING: Unsupported game, some features or values might behave incorrectly");
-        telemetry->game_ver_warn = true;
-    }
-
-    // Register for events. Note that failure to register those basic events
-    // likely indicates invalid usage of the api or some critical problem. As the
-    // example requires all of them, we can not continue if the registration fails.
-
-    const bool events_registered =
-        (version_params->register_for_event(SCS_TELEMETRY_EVENT_frame_start, telemetry_frame_start, NULL) == SCS_RESULT_ok) &&
-        (version_params->register_for_event(SCS_TELEMETRY_EVENT_frame_end, telemetry_frame_end, NULL) == SCS_RESULT_ok) &&
-        (version_params->register_for_event(SCS_TELEMETRY_EVENT_paused, telemetry_pause, NULL) == SCS_RESULT_ok) &&
-        (version_params->register_for_event(SCS_TELEMETRY_EVENT_started, telemetry_pause, NULL) == SCS_RESULT_ok)
-    ;
-    if (! events_registered) {
-
-        // Registrations created by unsuccessfull initialization are
-        // cleared automatically so we can simply exit.
-
-        version_params->common.log(SCS_LOG_TYPE_error, "Unable to register event callbacks");
-        return SCS_RESULT_generic_error;
-    }
-
-    // Register for the configuration info. As this example only prints the retrieved
-    // data, it can operate even if that fails.
-
-    version_params->register_for_event(SCS_TELEMETRY_EVENT_configuration, telemetry_configuration, NULL);
-
-    // Register for channels. The channel might be missing if the game does not support
-    // it (SCS_RESULT_not_found) or if does not support the requested type
-    // (SCS_RESULT_unsupported_type). For purpose of this example we ignore the failues
-    // so the unsupported channels will remain at theirs default value.
-
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_world_placement, SCS_U32_NIL, SCS_VALUE_TYPE_dplacement, SCS_TELEMETRY_CHANNEL_FLAG_no_value, telemetry_store_dplacement, telemetry);
-
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_speed, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->speed);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_cruise_control, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->cctrl);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_engine_rpm, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->rpm);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_engine_gear, SCS_U32_NIL, SCS_VALUE_TYPE_s32, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_s32, &telemetry->gear);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_displayed_gear, SCS_U32_NIL, SCS_VALUE_TYPE_s32, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_s32, &telemetry->gear_disp);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_effective_clutch, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->clutch_eff);
-
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->fuel);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel_warning, SCS_U32_NIL, SCS_VALUE_TYPE_bool, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_bool, &telemetry->fuel_warn);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel_average_consumption, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float_nz, &telemetry->fuel_avg);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel_range, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->fuel_range);
-
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_odometer, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->odometer);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_navigation_distance, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->nav_dist);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_navigation_time, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->nav_eta);
-    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_navigation_speed_limit, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->nav_slimit);
-
-    // Set the structure with defaults.
-
-    last_timestamp = static_cast<scs_timestamp_t>(-1);
-
-    // Initially the game is paused.
-
-    telemetry->paused = true;
-    return SCS_RESULT_ok;
-}
-
-/**
- * @brief Telemetry API deinitialization function.
- *
- * See scssdk_telemetry.h
- */
-SCSAPI_VOID scs_telemetry_shutdown(void)
-{
-    // Any cleanup needed. The registrations will be removed automatically
-    // so there is no need to do that manually.
-
-    drop_shm();
-}
-
-// Cleanup
-
-#ifdef _WIN32
-BOOL APIENTRY DllMain(
-    HMODULE module,
-    DWORD  reason_for_call,
-    LPVOID reseved
-)
-{
-    if (reason_for_call == DLL_PROCESS_DETACH) {
-        drop_shm();
-    }
-    return TRUE;
-}
-#endif
-
-#ifdef __linux__
-void __attribute__ ((destructor)) unload(void)
-{
-    drop_shm();
-}
-#endif
diff --git a/teleshmem.cpp b/teleshmem.cpp
new file mode 100644 (file)
index 0000000..83b4385
--- /dev/null
@@ -0,0 +1,500 @@
+/**
+ * @brief Simple logger.
+ *
+ * Writes the output into file inside the current directory.
+ */
+
+// Windows stuff.
+
+#ifdef _WIN32
+#  define WINVER 0x0500
+#  define _WIN32_WINNT 0x0500
+#  include <windows.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+// SDK
+
+#include "scssdk_telemetry.h"
+#include "eurotrucks2/scssdk_eut2.h"
+#include "eurotrucks2/scssdk_telemetry_eut2.h"
+#include "amtrucks/scssdk_ats.h"
+#include "amtrucks/scssdk_telemetry_ats.h"
+
+//LINUX
+
+#include <unistd.h>
+#include <signal.h>
+
+#define UNUSED(x)
+
+/**
+ * @brief Combined telemetry data and shared memory management.
+ */
+
+#include "telemetry.h"
+#include "shmget.h"
+
+static struct telemetry_state_t *telemetry = NULL;
+
+static bool init_shm(void)
+{
+    if ( NULL == telemetry ) {
+        void *p;
+        p = init_shmput( TELE_SHM_KEY, sizeof *telemetry );
+        telemetry = static_cast<struct telemetry_state_t *>(p);
+    }
+    assert( NULL != telemetry );
+    return NULL != telemetry;
+}
+
+static void drop_shm(void)
+{
+    if ( NULL != telemetry ) {
+        release_shm( TELE_SHM_KEY, telemetry );
+        telemetry = NULL;
+    }
+}
+
+/**
+ * @brief Last timestamp we received.
+ */
+scs_timestamp_t last_timestamp = static_cast<scs_timestamp_t>(-1);
+
+
+/**
+ * @brief Logging to external file.
+ */
+
+#ifdef LOGGING
+#include <stdarg.h>
+
+static FILE *logfp = NULL;
+
+static int log_open( void ) {
+    if ( NULL == logfp ) {
+        int fd;
+        char tmpl[] = "/tmp/ets2tele_XXXXXX.log";
+        if ( 0 > ( fd =  mkstemps( tmpl, 4 ) ) )
+            return -1;
+        logfp = fdopen( fd, "a" );
+    }
+    return 0;
+}
+
+static void log_close( void ) {
+    if ( logfp ) {
+        fclose( logfp );
+        logfp = NULL;
+    }
+}
+
+static void log_print(const char *const fmt, ...)
+{
+    if ( logfp ) {
+        va_list arglist;
+        va_start( arglist, fmt );
+        vfprintf( logfp, fmt, arglist );
+        va_end( arglist );
+        fflush( logfp );
+    }
+}
+#else
+#define log_open()
+#define log_close()
+#define log_print(...)
+#endif // def LOGGING
+
+
+/**
+ * @brief Handling of individual events.
+ */
+
+SCSAPI_VOID telemetry_frame_start(const scs_event_t UNUSED(event), const void *const event_info, const scs_context_t UNUSED(context))
+{
+    const struct scs_telemetry_frame_start_t *const info = static_cast<const scs_telemetry_frame_start_t *>(event_info);
+
+    // The following processing of the timestamps is done so the output
+    // from this plugin has continuous time, it is not necessary otherwise.
+
+    // When we just initialized itself, assume that the time started
+    // just now.
+
+    if (last_timestamp == static_cast<scs_timestamp_t>(-1)) {
+        last_timestamp = info->paused_simulation_time;
+    }
+
+    // The timer might be sometimes restarted (e.g. after load) while
+    // we want to provide continuous time on our output.
+
+    if (info->flags & SCS_TELEMETRY_FRAME_START_FLAG_timer_restart) {
+        last_timestamp = 0;
+    }
+
+    // Advance the timestamp by delta since last frame.
+
+    telemetry->timestamp += (info->paused_simulation_time - last_timestamp);
+    last_timestamp = info->paused_simulation_time;
+
+    // The raw values.
+
+    telemetry->raw_rendering_timestamp = info->render_time;
+    telemetry->raw_simulation_timestamp = info->simulation_time;
+    telemetry->raw_paused_simulation_timestamp = info->paused_simulation_time;
+}
+
+SCSAPI_VOID telemetry_frame_end(const scs_event_t UNUSED(event), const void *const UNUSED(event_info), const scs_context_t UNUSED(context))
+{
+}
+
+SCSAPI_VOID telemetry_pause(const scs_event_t event, const void *const UNUSED(event_info), const scs_context_t UNUSED(context))
+{
+    telemetry->paused = (event == SCS_TELEMETRY_EVENT_paused);
+}
+
+SCSAPI_VOID telemetry_configuration(const scs_event_t event, const void *const event_info, const scs_context_t UNUSED(context))
+{
+    // Here we just print the configuration info.
+
+    const struct scs_telemetry_configuration_t *const info = static_cast<const scs_telemetry_configuration_t *>(event_info);
+    log_print("Configuration: %s\n", info->id);
+
+    for (const scs_named_value_t *current = info->attributes; current->name; ++current) {
+        log_print("  %s", current->name);
+        if (current->index != SCS_U32_NIL) {
+            log_print("[%u]", static_cast<unsigned>(current->index));
+        }
+        log_print(" : ");
+        switch (current->value.type) {
+            case SCS_VALUE_TYPE_INVALID: {
+                log_print("none\n");
+                break;
+            }
+            case SCS_VALUE_TYPE_bool: {
+                log_print("bool = %s\n", current->value.value_bool.value ? "true" : "false");
+                break;
+            }
+            case SCS_VALUE_TYPE_s32: {
+                log_print("s32 = %d\n", static_cast<int>(current->value.value_s32.value));
+                break;
+            }
+            case SCS_VALUE_TYPE_u32: {
+                log_print("u32 = %u\n", static_cast<unsigned>(current->value.value_u32.value));
+                break;
+            }
+            case SCS_VALUE_TYPE_u64: {
+                log_print("u64 = %" SCS_PF_U64 "\n", current->value.value_u64.value);
+                break;
+            }
+            case SCS_VALUE_TYPE_float: {
+                log_print("float = %f\n", current->value.value_float.value);
+                break;
+            }
+            case SCS_VALUE_TYPE_double: {
+                log_print("double = %f\n", current->value.value_double.value);
+                break;
+            }
+            case SCS_VALUE_TYPE_fvector: {
+                log_print(
+                    "fvector = (%f,%f,%f)\n",
+                    current->value.value_fvector.x,
+                    current->value.value_fvector.y,
+                    current->value.value_fvector.z
+                );
+                break;
+            }
+            case SCS_VALUE_TYPE_dvector: {
+                log_print(
+                    "dvector = (%f,%f,%f)\n",
+                    current->value.value_dvector.x,
+                    current->value.value_dvector.y,
+                    current->value.value_dvector.z
+                );
+                break;
+            }
+            case SCS_VALUE_TYPE_euler: {
+                log_print(
+                    "euler = h:%f p:%f r:%f\n",
+                    current->value.value_euler.heading * 360.0f,
+                    current->value.value_euler.pitch * 360.0f,
+                    current->value.value_euler.roll * 360.0f
+                );
+                break;
+            }
+            case SCS_VALUE_TYPE_fplacement: {
+                log_print(
+                    "fplacement = (%f,%f,%f) h:%f p:%f r:%f\n",
+                    current->value.value_fplacement.position.x,
+                    current->value.value_fplacement.position.y,
+                    current->value.value_fplacement.position.z,
+                    current->value.value_fplacement.orientation.heading * 360.0f,
+                    current->value.value_fplacement.orientation.pitch * 360.0f,
+                    current->value.value_fplacement.orientation.roll * 360.0f
+                );
+                break;
+            }
+            case SCS_VALUE_TYPE_dplacement: {
+                log_print(
+                    "dplacement = (%f,%f,%f) h:%f p:%f r:%f\n",
+                    current->value.value_dplacement.position.x,
+                    current->value.value_dplacement.position.y,
+                    current->value.value_dplacement.position.z,
+                    current->value.value_dplacement.orientation.heading * 360.0f,
+                    current->value.value_dplacement.orientation.pitch * 360.0f,
+                    current->value.value_dplacement.orientation.roll * 360.0f
+                );
+                break;
+            }
+            case SCS_VALUE_TYPE_string: {
+                log_print("string = %s\n", current->value.value_string.value);
+                break;
+            }
+            default: {
+                log_print("unknown\n");
+                break;
+            }
+        }
+    }
+}
+
+// Handling of individual channels.
+
+SCSAPI_VOID telemetry_store_dplacement(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
+{
+    assert(value);
+    assert(value->type == SCS_VALUE_TYPE_dplacement);
+    assert(context);
+    telemetry_state_t *const tele = static_cast<telemetry_state_t *>(context);
+
+    // This callback was registered with the SCS_TELEMETRY_CHANNEL_FLAG_no_value flag
+    // so it is called even when the value is not available.
+    if ( !value || value->type != SCS_VALUE_TYPE_dplacement ) {
+        tele->placement_available = false;
+        return;
+    }
+    tele->placement_available = true;
+    tele->x = value->value_dplacement.position.x;
+    tele->y = value->value_dplacement.position.y;
+    tele->z = value->value_dplacement.position.z;
+    tele->heading = value->value_dplacement.orientation.heading;
+    tele->pitch = value->value_dplacement.orientation.pitch;
+    tele->roll = value->value_dplacement.orientation.roll;
+}
+
+SCSAPI_VOID telemetry_store_float(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
+{
+    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
+    // so this callback is only called when a valid value is available.
+
+    assert(value);
+    assert(value->type == SCS_VALUE_TYPE_float);
+    assert(context);
+    *static_cast<float *>(context) = value->value_float.value;
+}
+
+SCSAPI_VOID telemetry_store_float_nz(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
+{
+    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
+    // so this callback is only called when a valid value is available.
+
+    assert(value);
+    assert(value->type == SCS_VALUE_TYPE_float);
+    assert(context);
+    if (value->value_float.value)
+        *static_cast<float *>(context) = value->value_float.value;
+}
+
+SCSAPI_VOID telemetry_store_s32(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
+{
+    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
+    // so this callback is only called when a valid value is available.
+
+    assert(value);
+    assert(value->type == SCS_VALUE_TYPE_s32);
+    assert(context);
+    *static_cast<int *>(context) = value->value_s32.value;
+}
+
+SCSAPI_VOID telemetry_store_bool(const scs_string_t name, const scs_u32_t index, const scs_value_t *const value, const scs_context_t context)
+{
+    // The SCS_TELEMETRY_CHANNEL_FLAG_no_value flag was not provided during registration
+    // so this callback is only called when a valid value is available.
+
+    assert(value);
+    assert(value->type == SCS_VALUE_TYPE_bool);
+    assert(context);
+    *static_cast<bool *>(context) = value->value_bool.value;
+}
+
+/**
+ * @brief Telemetry API initialization function.
+ *
+ * See scssdk_telemetry.h
+ */
+SCSAPI_RESULT scs_telemetry_init(const scs_u32_t version, const scs_telemetry_init_params_t *const params)
+{
+    log_open();
+
+    // We currently support only one version.
+
+    if (version != SCS_TELEMETRY_VERSION_1_00) {
+        return SCS_RESULT_unsupported;
+    }
+
+    const scs_telemetry_init_params_v100_t *const version_params = static_cast<const scs_telemetry_init_params_v100_t *>(params);
+    if ( !init_shm() ) {
+        log_print( "ERROR: Unable to initialize shared memory\n" );
+        version_params->common.log(SCS_LOG_TYPE_error, "Unable to initialize shared memory");
+        return SCS_RESULT_generic_error;
+    }
+
+    // Check application version. Note that this example uses fairly basic channels which are likely to be supported
+    // by any future SCS trucking game however more advanced application might want to at least warn the user if there
+    // is game or version they do not support.
+    snprintf( telemetry->game_id, sizeof telemetry->game_id, "%s", version_params->common.game_id );
+    telemetry->game_major_ver = SCS_GET_MAJOR_VERSION(version_params->common.game_version);
+    telemetry->game_minor_ver = SCS_GET_MINOR_VERSION(version_params->common.game_version);
+
+    if (strcmp(version_params->common.game_id, SCS_GAME_ID_EUT2) == 0) {
+
+        // Below the minimum version there might be some missing features (only minor change) or
+        // incompatible values (major change).
+
+        const scs_u32_t MINIMAL_VERSION = SCS_TELEMETRY_EUT2_GAME_VERSION_1_00;
+        if (version_params->common.game_version < MINIMAL_VERSION) {
+            log_print("WARNING: Too old version of the game, some features might behave incorrectly\n");
+            telemetry->game_ver_warn = true;
+        }
+
+        // Future versions are fine as long the major version is not changed.
+
+        const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_EUT2_GAME_VERSION_CURRENT;
+        if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
+            log_print("WARNING: Too new major version of the game, some features might behave incorrectly\n");
+            telemetry->game_ver_warn = true;
+        }
+    }
+    else if (strcmp(version_params->common.game_id, SCS_GAME_ID_ATS) == 0) {
+
+        // Bellow the minimum version there might be some missing features (only minor change) or
+        // incompatible values (major change).
+
+        const scs_u32_t MINIMAL_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_1_00;
+        if (version_params->common.game_version < MINIMAL_VERSION) {
+            log_print("WARNING: Too old version of the game, some features might behave incorrectly\n");
+            telemetry->game_ver_warn = true;
+        }
+
+        // Future versions are fine as long the major version is not changed.
+
+        const scs_u32_t IMPLEMENTED_VERSION = SCS_TELEMETRY_ATS_GAME_VERSION_CURRENT;
+        if (SCS_GET_MAJOR_VERSION(version_params->common.game_version) > SCS_GET_MAJOR_VERSION(IMPLEMENTED_VERSION)) {
+            log_print("WARNING: Too new major version of the game, some features might behave incorrectly\n");
+            telemetry->game_ver_warn = true;
+        }
+    }
+    else {
+        log_print("WARNING: Unsupported game, some features or values might behave incorrectly\n");
+        telemetry->game_ver_warn = true;
+    }
+
+    // Register for events. Note that failure to register those basic events
+    // likely indicates invalid usage of the api or some critical problem. As the
+    // example requires all of them, we can not continue if the registration fails.
+
+    const bool events_registered =
+        (version_params->register_for_event(SCS_TELEMETRY_EVENT_frame_start, telemetry_frame_start, NULL) == SCS_RESULT_ok) &&
+        (version_params->register_for_event(SCS_TELEMETRY_EVENT_frame_end, telemetry_frame_end, NULL) == SCS_RESULT_ok) &&
+        (version_params->register_for_event(SCS_TELEMETRY_EVENT_paused, telemetry_pause, NULL) == SCS_RESULT_ok) &&
+        (version_params->register_for_event(SCS_TELEMETRY_EVENT_started, telemetry_pause, NULL) == SCS_RESULT_ok)
+    ;
+    if (! events_registered) {
+
+        // Registrations created by unsuccessfull initialization are
+        // cleared automatically so we can simply exit.
+
+        version_params->common.log(SCS_LOG_TYPE_error, "Unable to register event callbacks");
+        return SCS_RESULT_generic_error;
+    }
+
+    // Register for the configuration info. As this example only prints the retrieved
+    // data, it can operate even if that fails.
+
+    version_params->register_for_event(SCS_TELEMETRY_EVENT_configuration, telemetry_configuration, NULL);
+
+    // Register for channels. The channel might be missing if the game does not support
+    // it (SCS_RESULT_not_found) or if does not support the requested type
+    // (SCS_RESULT_unsupported_type). For purpose of this example we ignore the failues
+    // so the unsupported channels will remain at theirs default value.
+
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_world_placement, SCS_U32_NIL, SCS_VALUE_TYPE_dplacement, SCS_TELEMETRY_CHANNEL_FLAG_no_value, telemetry_store_dplacement, telemetry);
+
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_speed, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->speed);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_cruise_control, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->cctrl);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_engine_rpm, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->rpm);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_engine_gear, SCS_U32_NIL, SCS_VALUE_TYPE_s32, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_s32, &telemetry->gear);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_displayed_gear, SCS_U32_NIL, SCS_VALUE_TYPE_s32, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_s32, &telemetry->gear_disp);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_effective_clutch, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->clutch_eff);
+
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->fuel);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel_warning, SCS_U32_NIL, SCS_VALUE_TYPE_bool, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_bool, &telemetry->fuel_warn);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel_average_consumption, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float_nz, &telemetry->fuel_avg);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_fuel_range, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->fuel_range);
+
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_odometer, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->odometer);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_navigation_distance, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->nav_dist);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_navigation_time, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->nav_eta);
+    version_params->register_for_channel(SCS_TELEMETRY_TRUCK_CHANNEL_navigation_speed_limit, SCS_U32_NIL, SCS_VALUE_TYPE_float, SCS_TELEMETRY_CHANNEL_FLAG_none, telemetry_store_float, &telemetry->nav_slimit);
+
+    // Set the structure with defaults.
+
+    last_timestamp = static_cast<scs_timestamp_t>(-1);
+
+    // Initially the game is paused.
+
+    telemetry->paused = true;
+    return SCS_RESULT_ok;
+}
+
+/**
+ * @brief Telemetry API deinitialization function.
+ *
+ * See scssdk_telemetry.h
+ */
+SCSAPI_VOID scs_telemetry_shutdown(void)
+{
+    // Any cleanup needed. The registrations will be removed automatically
+    // so there is no need to do that manually.
+
+    drop_shm();
+    log_close();
+}
+
+// Cleanup
+
+#ifdef _WIN32
+BOOL APIENTRY DllMain(
+    HMODULE module,
+    DWORD  reason_for_call,
+    LPVOID reseved
+)
+{
+    if (reason_for_call == DLL_PROCESS_DETACH) {
+        drop_shm();
+        log_close();
+    }
+    return TRUE;
+}
+#endif
+
+#ifdef __linux__
+void __attribute__ ((destructor)) unload(void)
+{
+    drop_shm();
+    log_close();
+}
+#endif