// // Copyright(c) 2021 LIACS, Leiden University // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Author: // // Richard M.K. van Dijk // Research sofware engineer // E: m.k.van.dijk@liacs.leidenuniv.nl // // Leiden University, // Faculty of Math and Natural Sciences, // Leiden Institute of Advanced Computer Science (LIACS) // Snellius building | Niels Bohrweg 1 | 2333 CA Leiden // The Netherlands // #include <tizen.h> #include <service_app.h> #include "sensorservice.h" #include <sensor.h> #include <locations.h> #include <device/battery.h> #include <device/power.h> #include <time.h> #include <device/haptic.h> #include <stdio.h> #define VERSION_NUMBER "v1.0.2" // Testing mode for privacy circle: true means record all data but indicate outside P, inside I or unknown ? of privacy circle #define TESTING_MODE true // Sensor service states #define WAITING 0 #define MEASURING 1 // Unique number #define MIN_UNIQUE_IDENTIFIER_WATCH 001 #define MAX_UNIQUE_IDENTIFIER_WATCH 999 #define DEFAULT_UNIQUE_IDENTIFIER_WATCH "0000" // Sensor intervals in milli seconds (unsigned int) #define MIN_INTERVAL_ACCELEROMETER 1 #define MAX_INTERVAL_ACCELEROMETER 1000 #define DEFAULT_INTERVAL_ACCELEROMETER 25 #define MIN_INTERVAL_LINEAR_ACCELEROMETER 1 #define MAX_INTERVAL_LINEAR_ACCELEROMETER 1000 #define DEFAULT_INTERVAL_LINEAR_ACCELEROMETER 25 // Zero means switched off, take gravity accelerometer #define MIN_INTERVAL_GYROSCOPE 1 #define MAX_INTERVAL_GYROSCOPE 1000 #define DEFAULT_INTERVAL_GYROSCOPE 25 #define MIN_INTERVAL_BAROMETER 100 #define MAX_INTERVAL_BAROMETER 10000 #define DEFAULT_INTERVAL_BAROMETER 100 // Sensor intervals in seconds (unsigned int) #define MIN_INTERVAL_GPS 1 #define MAX_INTERVAL_GPS 10 #define DEFAULT_INTERVAL_GPS 1 #define MIN_BASE_LATITUDE -90.000000 #define MAX_BASE_LATITUDE 90.000000 #define DEFAULT_BASE_LATITUDE 52.169311 // Lorentz Center @ Snellius #define MIN_BASE_LONGITUDE -180.000000 #define MAX_BASE_LONGITUDE 180.000000 #define DEFAULT_BASE_LONGITUDE 4.456711 // Lorentz Center @ Snellius #define MIN_BASE_PRIVACY_DISTANCE 10 #define MAX_BASE_PRIVACY_DISTANCE 10000 #define DEFAULT_BASE_PRIVACY_DISTANCE 100 // Sensor write to sensor file interval in seconds (float) #define MIN_INTERVAL_WRITE 0.010 #define MAX_INTERVAL_WRITE 10.000 #define DEFAULT_INTERVAL_WRITE 0.050 #define START_DELAY_SENSOR_WRITE 0.225 // Configurable #define NR_SAMPLES_AGA 10000 struct _sensor_info { sensor_h sensor; sensor_listener_h sensor_listener; }; typedef struct _sensor_info sensorinfo_s; /** * * @brief Global variables starting with g_ * */ FILE *g_fd_aag = NULL; // sensor file for gravity- and linear accelerometer and gyroscope FILE *g_fd_bar = NULL; // sensor file for air pressure barometer FILE *g_fd_gps = NULL; // sensor file for GPS latitude, longitude static sensorinfo_s g_sensor_info_accelerometer; static sensorinfo_s g_sensor_info_gyroscope; static sensorinfo_s g_sensor_info_pressure; static sensorinfo_s g_sensor_info_linear_accelerometer; // Accelerometer, gyroscope, air-pressure, battery static float g_acce_x, g_acce_x_; // acceleration in m/s^2 static float g_acce_y, g_acce_y_; // acceleration in m/s^2 static float g_acce_z, g_acce_z_; // acceleration in m/s^2 static float g_lin_acce_x, g_lin_acce_x_; // acceleration in m/s^2 static float g_lin_acce_y, g_lin_acce_y_; // acceleration in m/s^2 static float g_lin_acce_z, g_lin_acce_z_; // acceleration in m/s^2 static float g_gyro_x, g_gyro_x_; // rotation speed in degrees ratio per second (-2^15/+2^15) static float g_gyro_y, g_gyro_y_; // rotation speed in degrees ratio per second (-2^15/+2^15) static float g_gyro_z, g_gyro_z_; // rotation speed in degrees ratio per second (-2^15/+2^15) static float g_pressure, g_pressure_; // barometer air pressure in milli bar static int g_battery; // remaining power of battery in percentage of maximum capacity, 5% = low-battery, applications will switch off static double g_time_; // The time when the last write timer wrote the sensor values // GPS static location_manager_h g_manager; static double g_latitude; // horizontal coordinate dd.mm.ss.ff in degrees, minutes, seconds and fraction of a second static double g_longitude; // vertical coordinate dd.mm.ss.ff in degrees, minutes, seconds and fraction of a second static double g_altitude; // height in meters static double g_climb; // speed of climb in km/h static double g_direction; // movement direction as degree with the North as zero orientation static double g_speed; // movement speed in km/h static double g_horizontal; // accuracy in meters for the horizontal plain static double g_vertical; // accuracy in meters for the vertical plain static location_accuracy_level_e g_level; // number of accuracy of location determination between 0 and 6 static location_boundary_state_e g_gps_base_bound_state = LOCATIONS_ERROR_GPS_SETTING_OFF; static location_bounds_h g_gps_base_bounds; // Varying globals static double g_base_write_sensor_readings_time = 0.0; static char g_timestring[20] = "YYYY MM DD HH mm ss"; static unsigned int g_personid = 0; static int g_service_state = WAITING; Ecore_Timer *g_timer_id; /** * * @brief Vibrate wearable for 1 second, intensity 30 (0-100). * */ void vibrate() { haptic_device_h device_handle; int device_number; int err = device_haptic_get_count(&device_number); dlog_print(DLOG_INFO, LOG_TAG, "Vibrate device number %d %d", device_number, err); device_haptic_open(device_number, &device_handle); device_haptic_vibrate(device_handle, 1000, 30, NULL); // TODO: search for effect examples device_haptic_stop(device_handle, NULL); return; } /** * * @brief Timer callback to align the time and sensor readings. * * @details ISO 8601 time format YYYY:MM:DD HH:mm:ss, 24h format, * but without : because Tizen Device Manager "pull file" does not accept this. * * Reference: http://www.cplusplus.com/reference/ctime/strftime/ * */ void get_timestring() { time_t rawtime; time (&rawtime); struct tm * timeinfo; timeinfo = localtime (&rawtime); strftime (g_timestring, 20, "%Y %m %d %H %M %S", timeinfo); return; } /** * * @brief Execute a Linux command. * * @detailed The command is given on the root dir because change of directory is not supported. * So, to get a list of files from a certain dir can be done with the command "ls -l /opt/var/tmp". * * If you are not interested in the output, use system("ls -l /opt/var/tmp") != NULL; * * To remove a file use for example "rm /opt/var/tmp/000 * aga.dat" * * NOTE: You can only remove a file with the app which created this file. * * To make a file use "ls -l /opt/var/tmp > /opt/var/tmp/output.txt" * * The directory /opt/var/tmp has set all permissions (read, write and execute). * * * @return 0 if okay, -1 if command failed */ int linux_command(const char* command, char* linux_output) { FILE *fd = popen(command, "r"); if (fd < 0) { dlog_print(DLOG_ERROR, LOG_TAG, "linux command %s failed %d", command, fd); return -1; } char line[256]; line[0] = 0; linux_output[0] = 0; while(fgets(line, sizeof(line), fd) != NULL) { strcat(linux_output, line); } strcat(linux_output, "\n"); pclose(fd); return 0; } /** * * @brief Read and write the configuration file. * * @details * * line1 - unique_identifier_watch_int <value in %3d><\n> * line2 - accelerometer_interval_ms <value in %3d><\n> * line3 - linear_accelerometer_interval_ms <value in %3d><\n> * line4 - gyroscope_interval_ms <value in %3d><\n> * line5 - barometer_interval_ms <value in %3d><\n> * line6 - gps_interval_seconds <value in %2d><\n> * line7 - write_interval_ms <value in %3d><\n> * line8 - gps_base_point_latitude <value in %2.6f> _longitude <value in %2.6f><\n> * line9 - gps_base_privacy_distance <><\n> * * If the parameters have the value of zero, the sensor or service will be disabled. * */ static char g_unique_identifier_watch[32] = DEFAULT_UNIQUE_IDENTIFIER_WATCH; static unsigned int g_accelerometer_interval_ms = DEFAULT_INTERVAL_ACCELEROMETER; static unsigned int g_lin_accelerometer_interval_ms = DEFAULT_INTERVAL_LINEAR_ACCELEROMETER; static unsigned int g_gyroscope_interval_ms = DEFAULT_INTERVAL_GYROSCOPE; static unsigned int g_barometer_interval_ms = DEFAULT_INTERVAL_BAROMETER; static unsigned int g_gps_interval_seconds = DEFAULT_INTERVAL_GPS; static double g_gps_base_point_latitude = DEFAULT_BASE_LATITUDE; static double g_gps_base_point_longitude = DEFAULT_BASE_LONGITUDE; static unsigned int g_gps_base_privacy_distance = DEFAULT_BASE_PRIVACY_DISTANCE; static double g_write_interval_seconds = DEFAULT_INTERVAL_WRITE; /** * * @brief If a parameter is zero, let it be, it is used to disable to corresponding sensor. * * @details The validation does not throw errors but corrects invalid values by default settings (robust validation type). * * The configuration file will be read from /opt/var/tmp/ folder. This folder permits the developer account to write * a configuration file to while the data folder of the sensor service does not. The file cannot be deleted by the developer * account. This is good because the configuration file contains the unique identifier of the watch. * * However, it can be over-written. Do take care not to change the unique identifier of the watch. * */ static void validate_configuration_file_contents() { if(strcmp(g_unique_identifier_watch, "") == 0) snprintf(g_unique_identifier_watch, sizeof(g_unique_identifier_watch), "%s\n", DEFAULT_UNIQUE_IDENTIFIER_WATCH); if(g_accelerometer_interval_ms != 0) if(!(MIN_INTERVAL_ACCELEROMETER <= g_accelerometer_interval_ms && g_accelerometer_interval_ms <= MAX_INTERVAL_ACCELEROMETER)) g_accelerometer_interval_ms = DEFAULT_INTERVAL_ACCELEROMETER; if(g_lin_accelerometer_interval_ms != 0) if(!(MIN_INTERVAL_ACCELEROMETER <= g_lin_accelerometer_interval_ms && g_lin_accelerometer_interval_ms <= MAX_INTERVAL_ACCELEROMETER)) g_lin_accelerometer_interval_ms = DEFAULT_INTERVAL_LINEAR_ACCELEROMETER; if(g_gyroscope_interval_ms != 0) if(!(MIN_INTERVAL_GYROSCOPE <= g_gyroscope_interval_ms && g_gyroscope_interval_ms <= MAX_INTERVAL_GYROSCOPE)) g_gyroscope_interval_ms = DEFAULT_INTERVAL_GYROSCOPE; if(g_barometer_interval_ms != 0) if(!(MIN_INTERVAL_BAROMETER <= g_barometer_interval_ms && g_barometer_interval_ms <= MAX_INTERVAL_BAROMETER)) g_barometer_interval_ms = DEFAULT_INTERVAL_BAROMETER; if(g_gps_interval_seconds != 0) if(!(MIN_INTERVAL_GPS <= g_gps_interval_seconds && g_gps_interval_seconds <= MAX_INTERVAL_GPS)) g_gps_interval_seconds = DEFAULT_INTERVAL_GPS; if(g_gps_base_point_latitude != 0) if(!(MIN_BASE_LATITUDE <= g_gps_base_point_latitude && g_gps_base_point_latitude <= MAX_BASE_LATITUDE)) g_gps_base_point_latitude = DEFAULT_BASE_LATITUDE; if(g_gps_base_point_longitude != 0) if(!(MIN_BASE_LONGITUDE <= g_gps_base_point_longitude && g_gps_base_point_longitude <= MAX_BASE_LONGITUDE)) g_gps_base_point_longitude = DEFAULT_BASE_LONGITUDE; if(g_gps_base_privacy_distance != 0) if(!(MIN_BASE_PRIVACY_DISTANCE <= g_gps_base_privacy_distance && g_gps_base_privacy_distance <= MAX_BASE_PRIVACY_DISTANCE)) g_gps_base_privacy_distance = DEFAULT_BASE_PRIVACY_DISTANCE; if(!(MIN_INTERVAL_WRITE <= g_write_interval_seconds && g_write_interval_seconds <= MAX_INTERVAL_WRITE)) g_write_interval_seconds = DEFAULT_INTERVAL_WRITE; return; } static void read_configuration_file() { char* data_path = NULL; char configurationfilename[256]; data_path = "/opt/var/tmp/"; // This folder has write permission for the developer account used by the host snprintf(configurationfilename, 256, "%sconfiguration.dat", data_path); dlog_print(DLOG_INFO, LOG_TAG, "Data path + configuration filename for read: %s", configurationfilename); FILE *fd = fopen(configurationfilename, "r"); if(fd == NULL) { dlog_print(DLOG_ERROR, LOG_TAG, "Could not open configuration file for read"); return; } fscanf(fd, "unique_identifier_watch_str %s\n", &g_unique_identifier_watch); fscanf(fd, "accelerometer_interval_ms_int %u\n", &g_accelerometer_interval_ms); fscanf(fd, "linear_accelerometer_interval_ms_int %u\n", &g_lin_accelerometer_interval_ms); fscanf(fd, "gyroscope_interval_ms_int %u\n", &g_gyroscope_interval_ms); fscanf(fd, "barometer_interval_ms_int %u\n", &g_barometer_interval_ms); fscanf(fd, "gps_interval_seconds_int %u\n", &g_gps_interval_seconds); fscanf(fd, "write_interval_seconds_float %lf\n", &g_write_interval_seconds); fscanf(fd, "gps_base_point_latitude %lf _longitude %lf\n", &g_gps_base_point_latitude, &g_gps_base_point_longitude); fscanf(fd, "gps_base_privacy_distance_meter_int %u\n", &g_gps_base_privacy_distance); fclose(fd); validate_configuration_file_contents(); return; } static void write_configuration_file() { char* data_path = NULL; char configurationfilename[256]; data_path = app_get_data_path(); snprintf(configurationfilename, 256, "%s%03d %s %s con.dat", data_path, g_personid, g_timestring, g_unique_identifier_watch); dlog_print(DLOG_INFO, LOG_TAG, "Data path + configuration filename for write: %s", configurationfilename); FILE *fd = fopen(configurationfilename, "w"); if(fd == NULL) { dlog_print(DLOG_ERROR, LOG_TAG, "Could not open current settings file for write"); return; } fprintf(fd, "version number_str %s\n", VERSION_NUMBER); fprintf(fd, "unique_identifier_watch_str %s\n", g_unique_identifier_watch); fprintf(fd, "accelerometer_interval_ms_int %3u\n", g_accelerometer_interval_ms); fprintf(fd, "linear_accelerometer_interval_ms_int %3u\n", g_lin_accelerometer_interval_ms); fprintf(fd, "gyroscope_interval_ms_int %3u\n", g_gyroscope_interval_ms); fprintf(fd, "barometer_interval_ms_int %3u\n", g_barometer_interval_ms); fprintf(fd, "gps_interval_seconds_int %2u\n", g_gps_interval_seconds); fprintf(fd, "write_interval_seconds_float %2.3f\n", g_write_interval_seconds); fprintf(fd, "gps_base_point_latitude %2.6f _longitude %2.6f\n", g_gps_base_point_latitude, g_gps_base_point_longitude); fprintf(fd, "gps_base_privacy_distance_meter_int %4u\n", g_gps_base_privacy_distance); fprintf(fd, "\n"); fprintf(fd, "Notes:\n"); fprintf(fd, " Lorentz Center @ Snellius Leiden, latitude %2.6f longitude %2.6f\n", DEFAULT_BASE_LATITUDE, DEFAULT_BASE_LONGITUDE); fprintf(fd, " ...\n"); fclose(fd); return; } /** * * @brief Open and close the sensor files (aag = accelerometer+gyro, bar = barometer, gps = gps data). * */ static void open_new_sensor_files() { char* data_path = NULL; char aagfilename[256]; char barfilename[256]; char gpsfilename[256]; get_timestring(); g_base_write_sensor_readings_time = 0.0; // AAG sensor file data_path = app_get_data_path(); snprintf(aagfilename, 256, "%s%03d %s %s aag.dat", data_path, g_personid, g_timestring, g_unique_identifier_watch); dlog_print(DLOG_INFO, LOG_TAG, "Data path + aag filename: %s", aagfilename); g_fd_aag = fopen(aagfilename, "w"); fprintf(g_fd_aag, "%03d %s %s\n", g_personid, g_unique_identifier_watch, g_timestring); if(g_lin_accelerometer_interval_ms == 0) fprintf(g_fd_aag, "time, acce_x, acce_y, acce_z, gyro_x, gyro_y, gyro_z, private\n"); else fprintf(g_fd_aag, "time, acce_x, acce_y, acce_z, lin_acce_x, lin_acce_y, lin_acce_z, gyro_x, gyro_y, gyro_z, private\n"); // BAR sensor file snprintf(barfilename, 256, "%s%03d %s %s bar.dat", data_path, g_personid, g_timestring, g_unique_identifier_watch); dlog_print(DLOG_INFO, LOG_TAG, "Data path + bar filename: %s", barfilename); g_fd_bar = fopen(barfilename, "w"); fprintf(g_fd_bar, "%03d %s %s\n", g_personid, g_unique_identifier_watch, g_timestring); fprintf(g_fd_bar, "time, baro, battery\n"); // GPS sensor file snprintf(gpsfilename, 256, "%s%03d %s %s gps.dat", data_path, g_personid, g_timestring, g_unique_identifier_watch); dlog_print(DLOG_INFO, LOG_TAG, "Data path + gps filename: %s", gpsfilename); g_fd_gps = fopen(gpsfilename, "w"); fprintf(g_fd_gps, "%03d %s %s\n", g_personid, g_unique_identifier_watch, g_timestring); fprintf(g_fd_gps, "time, latitude, longitude, accuracy, private\n"); return; } static void close_sensor_files() { fclose(g_fd_aag); fclose(g_fd_bar); fclose(g_fd_gps); dlog_print(DLOG_INFO, LOG_TAG, "closed all sensor files"); } /** * * @brief When the GPS position is updated this callback is called every 1-10 seconds. * * If the privacy circle is set, the GPS position is not recorded if the position is outside of * this circle. The base point and privacy distance can be set by the configuration file. * */ static void write_gps_position_cb(double latitude, double longitude, double altitude, time_t timestamp, void *user_data) { double time = ecore_time_unix_get(); location_manager_get_location(g_manager, &g_altitude, &g_latitude, &g_longitude, &g_climb, &g_direction, &g_speed, &g_level, &g_horizontal, &g_vertical, ×tamp); if(g_gps_base_privacy_distance != 0 && g_gps_base_bound_state == LOCATIONS_BOUNDARY_IN) { fprintf(g_fd_gps, "%0.1f," "%0.6f,%0.6f," "%0.1f," "I\n", time - g_base_write_sensor_readings_time, g_latitude, g_longitude, g_horizontal); } if(TESTING_MODE && g_gps_base_privacy_distance != 0 && g_gps_base_bound_state == LOCATIONS_BOUNDARY_OUT) { fprintf(g_fd_gps, "%0.1f," "%0.6f,%0.6f," "%0.1f," "P\n", time - g_base_write_sensor_readings_time, g_latitude, g_longitude, g_horizontal); } if (g_gps_base_privacy_distance == 0 || (g_gps_base_bound_state != LOCATIONS_BOUNDARY_OUT && g_gps_base_bound_state != LOCATIONS_BOUNDARY_IN)) { fprintf(g_fd_gps, "%0.1f," "%0.6f,%0.6f," "%0.1f," "?\n", time - g_base_write_sensor_readings_time, g_latitude, g_longitude, g_horizontal); } } /** * * @brief Write the sensor values of gravity + linear accelerometer + gyroscope to file every x ms. * */ Eina_Bool write_sensor_readings_cb(void *data) { // Time of Watch in seconds and fraction of a second from January first of 1970 (Unix base time) double time = ecore_time_unix_get(); // Set the base time for sensors and gps, if not set. if(g_base_write_sensor_readings_time == 0.0) g_base_write_sensor_readings_time = time; // Remove duplicates based on minimal time difference with last write (0.002 seconds) if(time - g_time_ < 0.002) return ECORE_CALLBACK_RENEW; g_time_ = time; // Remove duplicates based on identical sensor values with last write if( fabsf(g_acce_x - g_acce_x_) < 0.0001 && fabsf(g_acce_y - g_acce_y_) < 0.0001 && fabsf(g_acce_z - g_acce_z_) < 0.0001 && fabsf(g_lin_acce_x - g_lin_acce_x_) < 0.0001 && fabsf(g_lin_acce_y - g_lin_acce_y_) < 0.0001 && fabsf(g_lin_acce_z - g_lin_acce_z_) < 0.0001 && fabsf(g_gyro_x - g_gyro_x_) < 0.0001 && fabsf(g_gyro_y - g_gyro_y_) < 0.0001 && fabsf(g_gyro_z - g_gyro_z_) < 0.0001 ) return ECORE_CALLBACK_RENEW; g_acce_x_ = g_acce_x; g_acce_y_ = g_acce_y; g_acce_z_ = g_acce_z; g_lin_acce_x_ = g_lin_acce_x; g_lin_acce_y_ = g_lin_acce_y; g_lin_acce_z_ = g_lin_acce_z; g_gyro_x_ = g_gyro_x; g_gyro_y_ = g_gyro_y; g_gyro_z_ = g_gyro_z; // If gps is set on and boundary set as well, switch in privacy mode if(g_gps_interval_seconds != 0 && g_gps_base_privacy_distance != 0 && g_gps_base_bound_state == LOCATIONS_BOUNDARY_IN) { if(g_lin_accelerometer_interval_ms == 0) { fprintf(g_fd_aag, "%0.3f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "I\n", time - g_base_write_sensor_readings_time, g_acce_x, g_acce_y, g_acce_z, g_gyro_x, g_gyro_y, g_gyro_z); } else { fprintf(g_fd_aag, "%0.3f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "I\n", time - g_base_write_sensor_readings_time, g_acce_x, g_acce_y, g_acce_z, g_lin_acce_x, g_lin_acce_y, g_lin_acce_z, g_gyro_x, g_gyro_y, g_gyro_z); } } if(TESTING_MODE && g_gps_interval_seconds != 0 && g_gps_base_privacy_distance != 0 && g_gps_base_bound_state == LOCATIONS_BOUNDARY_OUT) { if(g_lin_accelerometer_interval_ms == 0) { fprintf(g_fd_aag, "%0.3f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "P\n", time - g_base_write_sensor_readings_time, g_acce_x, g_acce_y, g_acce_z, g_gyro_x, g_gyro_y, g_gyro_z); } else { fprintf(g_fd_aag, "%0.3f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "P\n", time - g_base_write_sensor_readings_time, g_acce_x, g_acce_y, g_acce_z, g_lin_acce_x, g_lin_acce_y, g_lin_acce_z, g_gyro_x, g_gyro_y, g_gyro_z); } } // If gps is switched off the privacy mode cannot be maintained or bound state is not defined. if( g_gps_interval_seconds == 0 || (g_gps_base_bound_state != LOCATIONS_BOUNDARY_IN && g_gps_base_bound_state != LOCATIONS_BOUNDARY_OUT)) { if(g_lin_accelerometer_interval_ms == 0) { fprintf(g_fd_aag, "%0.3f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "?\n", time - g_base_write_sensor_readings_time, g_acce_x, g_acce_y, g_acce_z, g_gyro_x, g_gyro_y, g_gyro_z); } else { fprintf(g_fd_aag, "%0.3f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "%0.4f,%0.4f,%0.4f," "?\n", time - g_base_write_sensor_readings_time, g_acce_x, g_acce_y, g_acce_z, g_lin_acce_x, g_lin_acce_y, g_lin_acce_z, g_gyro_x, g_gyro_y, g_gyro_z); } } return ECORE_CALLBACK_RENEW; } /** * * @brief Write the sensor value of the barometer to file every x ms. * */ static void write_barometer_readings(void *data) { // Time of Watch in seconds and fraction of a second from January first of 1970 (Unix base time) double time = ecore_time_unix_get(); // Set the base time for sensors and gps, if not set. if(g_base_write_sensor_readings_time == 0.0) g_base_write_sensor_readings_time = time; // Remove duplicates based on minimal time difference with last write (0.002 seconds) if(time - g_time_ < 0.002) return; g_time_ = time; // Remove duplicates based on identical sensor values with last write if( fabsf(g_pressure - g_pressure_) < 0.0001 ) return; g_pressure_ = g_pressure; // If gps is set on and boundary set as well, switch in privacy mode if(g_gps_interval_seconds != 0 && g_gps_base_privacy_distance != 0 && g_gps_base_bound_state == LOCATIONS_BOUNDARY_IN) { fprintf(g_fd_bar, "%0.3f," "%0.3f,%d," "I\n", time - g_base_write_sensor_readings_time, g_pressure, g_battery); } if(TESTING_MODE && g_gps_interval_seconds != 0 && g_gps_base_privacy_distance != 0 && g_gps_base_bound_state == LOCATIONS_BOUNDARY_OUT) { fprintf(g_fd_bar, "%0.3f," "%0.3f,%d," "P\n", time - g_base_write_sensor_readings_time, g_pressure, g_battery); } // If gps is switched off the privacy mode cannot be maintained or bound state is not defined. if( g_gps_interval_seconds == 0 || (g_gps_base_bound_state != LOCATIONS_BOUNDARY_IN && g_gps_base_bound_state != LOCATIONS_BOUNDARY_OUT)) { fprintf(g_fd_bar, "%0.3f," "%0.3f,%d," "?\n", time - g_base_write_sensor_readings_time, g_pressure, g_battery); } return; } /** * * @brief Sensoring the values. * */ static void _get_new_accelerometer_value(sensor_h sensor, sensor_event_s *events, void *user_data) { g_acce_x = events->values[0]; g_acce_y = events->values[1]; g_acce_z = events->values[2]; return; } static void _get_new_gyroscope_value(sensor_h sensor, sensor_event_s *events, void *user_data) { g_gyro_x = events->values[0]; g_gyro_y = events->values[1]; g_gyro_z = events->values[2]; return; } static void _get_new_pressure_value(sensor_h sensor, sensor_event_s *events, void *user_data) { device_battery_get_percent(&g_battery); g_pressure = events->values[0]; write_barometer_readings(user_data); return; } static void _get_new_linear_accelerometer_value(sensor_h sensor, sensor_event_s *events, void *user_data) { g_lin_acce_x = events->values[0]; g_lin_acce_y = events->values[1]; g_lin_acce_z = events->values[2]; return; } /** * * @brief Start a particular sensor listener. * */ static void create_and_start_accelerometer() { sensor_get_default_sensor(SENSOR_ACCELEROMETER, &g_sensor_info_accelerometer.sensor); sensor_create_listener(g_sensor_info_accelerometer.sensor, &g_sensor_info_accelerometer.sensor_listener); sensor_listener_set_event_cb(g_sensor_info_accelerometer.sensor_listener, g_accelerometer_interval_ms, (sensor_event_cb)_get_new_accelerometer_value, NULL); sensor_listener_set_option(g_sensor_info_accelerometer.sensor_listener, SENSOR_OPTION_ALWAYS_ON); // SENSOR_OPTION_ON_IN_POWERSAVE_MODE sensor_error_e err = SENSOR_ERROR_NONE; err = sensor_listener_start(g_sensor_info_accelerometer.sensor_listener); dlog_print(DLOG_INFO, LOG_TAG, "Accelerometer sensor listener started with interval %d ms %d", g_accelerometer_interval_ms, err); return; } static void create_and_start_gyroscope() { sensor_get_default_sensor(SENSOR_GYROSCOPE, &g_sensor_info_gyroscope.sensor); sensor_create_listener(g_sensor_info_gyroscope.sensor, &g_sensor_info_gyroscope.sensor_listener); sensor_listener_set_event_cb(g_sensor_info_gyroscope.sensor_listener, g_gyroscope_interval_ms, (sensor_event_cb)_get_new_gyroscope_value, NULL); sensor_listener_set_option(g_sensor_info_gyroscope.sensor_listener, SENSOR_OPTION_ALWAYS_ON); // SENSOR_OPTION_ON_IN_POWERSAVE_MODE sensor_error_e err = SENSOR_ERROR_NONE; err = sensor_listener_start(g_sensor_info_gyroscope.sensor_listener); dlog_print(DLOG_INFO, LOG_TAG, "Gyroscope sensor listener started with interval %d ms %d", g_gyroscope_interval_ms, err); return; } static void create_and_start_barometer() { sensor_get_default_sensor(SENSOR_PRESSURE, &g_sensor_info_pressure.sensor); sensor_create_listener(g_sensor_info_pressure.sensor, &g_sensor_info_pressure.sensor_listener); sensor_listener_set_event_cb(g_sensor_info_pressure.sensor_listener, g_barometer_interval_ms, (sensor_event_cb)_get_new_pressure_value, NULL); sensor_listener_set_option(g_sensor_info_pressure.sensor_listener, SENSOR_OPTION_ALWAYS_ON); // SENSOR_OPTION_ON_IN_POWERSAVE_MODE sensor_error_e err = SENSOR_ERROR_NONE; err = sensor_listener_start(g_sensor_info_pressure.sensor_listener); dlog_print(DLOG_INFO, LOG_TAG, "Pressure sensor listener started with interval %d ms %d", g_barometer_interval_ms, err); return; } static void create_and_start_linear_accelerometer() { sensor_get_default_sensor(SENSOR_LINEAR_ACCELERATION, &g_sensor_info_linear_accelerometer.sensor); sensor_create_listener(g_sensor_info_linear_accelerometer.sensor, &g_sensor_info_linear_accelerometer.sensor_listener); sensor_listener_set_event_cb(g_sensor_info_linear_accelerometer.sensor_listener, g_lin_accelerometer_interval_ms, (sensor_event_cb)_get_new_linear_accelerometer_value, NULL); sensor_listener_set_option(g_sensor_info_linear_accelerometer.sensor_listener, SENSOR_OPTION_ALWAYS_ON); // SENSOR_OPTION_ON_IN_POWERSAVE_MODE sensor_error_e err = SENSOR_ERROR_NONE; err = sensor_listener_start(g_sensor_info_linear_accelerometer.sensor_listener); dlog_print(DLOG_INFO, LOG_TAG, "Linear accelerometer sensor listener started with interval %d ms %d", g_lin_accelerometer_interval_ms, err); return; } /** * * @brief Destroy sensor listeners according to the configuration file in order to save power. * */ static void stop_and_destroy_sensor_listeners() { if(g_accelerometer_interval_ms == 0) sensor_destroy_listener(g_sensor_info_accelerometer.sensor_listener); if(g_lin_accelerometer_interval_ms == 0) sensor_destroy_listener(g_sensor_info_linear_accelerometer.sensor_listener); if(g_gyroscope_interval_ms == 0) sensor_destroy_listener(g_sensor_info_gyroscope.sensor_listener); if(g_barometer_interval_ms == 0) sensor_destroy_listener(g_sensor_info_pressure.sensor_listener); return; } /** * * @brief Create, start, stop and destroy write timer which writes the sensor values to the sensor files. * */ static void create_and_start_write_timer() { g_timer_id = ecore_timer_add(START_DELAY_SENSOR_WRITE, write_sensor_readings_cb, NULL); ecore_timer_interval_set(g_timer_id, g_write_interval_seconds); dlog_print(DLOG_INFO, LOG_TAG, "Sensor timer writer started with interval %0.3f seconds", g_write_interval_seconds); return; } static void stop_and_destroy_write_timer() { ecore_timer_del(g_timer_id); dlog_print(DLOG_INFO, LOG_TAG, "Sensor timer writer deleted"); return; } /** * * @brief The callback is called when the watch in entering of leaving the circle. * * It produces a state which can be LOCATIONS_BOUNDARY_IN (entering the circle) or _OUT (leaving the circle). * */ static void enter_or_leave_measurement_circle_cb(location_boundary_state_e state, void *user_data) { g_gps_base_bound_state = state; } static void set_gps_base_privacy_distance() { location_coords_s center; center.latitude = g_gps_base_point_latitude; center.longitude = g_gps_base_point_longitude; location_bounds_create_circle(center, g_gps_base_privacy_distance, &g_gps_base_bounds); location_bounds_set_state_changed_cb(g_gps_base_bounds, enter_or_leave_measurement_circle_cb, NULL); location_manager_add_boundary(g_manager, g_gps_base_bounds); return; } static void unset_gps_base_privacy_distance() { location_bounds_destroy(g_gps_base_bounds); return; } /** * * @brief Create, start, stop and destroy GPS sensors * */ static void create_and_start_gps() { location_manager_create(LOCATIONS_METHOD_GPS, &g_manager); // LOCATIONS_METHOD_HYBRID -> results in instable aga values location_manager_set_position_updated_cb(g_manager, write_gps_position_cb, g_gps_interval_seconds, NULL); if(g_gps_base_privacy_distance != 0) set_gps_base_privacy_distance(); else unset_gps_base_privacy_distance(); sensor_error_e err = SENSOR_ERROR_NONE; err = location_manager_start(g_manager); // If location connection on settings watch is off, do not set a privacy circle if(err<0) g_gps_base_privacy_distance = 0; dlog_print(DLOG_INFO, LOG_TAG, "GPS sensor manager started with interval %d seconds %d", g_gps_interval_seconds, err); return; } static void stop_and_destroy_gps() { unset_gps_base_privacy_distance(); location_manager_destroy(g_manager); g_manager = NULL; dlog_print(DLOG_INFO, LOG_TAG, "GPS sensor manager stopped and destroyed"); return; } /** * * @brief Start or stop all sensors based on the configuration file. * */ static void start_sensors() { if(g_barometer_interval_ms != 0) create_and_start_barometer(); if(g_accelerometer_interval_ms != 0) create_and_start_accelerometer(); if(g_lin_accelerometer_interval_ms != 0) create_and_start_linear_accelerometer(); if(g_gyroscope_interval_ms != 0) create_and_start_gyroscope(); if(g_gps_interval_seconds != 0) create_and_start_gps(); if(g_write_interval_seconds > 0.0) create_and_start_write_timer(); // Let CPU run independent of display mode (do not terminate after power saving on). device_power_request_lock(POWER_LOCK_CPU, 0); // TODO: tests show this has no effect, test separately return; } static void stop_sensors() { if(g_write_interval_seconds > 0.0) stop_and_destroy_write_timer(); if(g_accelerometer_interval_ms != 0) sensor_destroy_listener(g_sensor_info_accelerometer.sensor_listener); if(g_lin_accelerometer_interval_ms != 0) sensor_destroy_listener(g_sensor_info_linear_accelerometer.sensor_listener); if(g_gyroscope_interval_ms != 0) sensor_destroy_listener(g_sensor_info_gyroscope.sensor_listener); if(g_barometer_interval_ms != 0) sensor_destroy_listener(g_sensor_info_pressure.sensor_listener); if(g_gps_interval_seconds != 0) stop_and_destroy_gps(); return; } /** * * @brief Pause/resume the main timer, location manager and sensor listeners temporary. * */ static void pause_sensors() { ecore_timer_freeze(g_timer_id); location_manager_stop(g_manager); sensor_listener_stop(g_sensor_info_linear_accelerometer.sensor_listener); sensor_listener_stop(g_sensor_info_accelerometer.sensor_listener); sensor_listener_stop(g_sensor_info_gyroscope.sensor_listener); sensor_listener_stop(g_sensor_info_pressure.sensor_listener); return; } static void resume_sensors() { // Resume from pause the main timer and location manager sensor_listener_stop(g_sensor_info_linear_accelerometer.sensor_listener); sensor_listener_stop(g_sensor_info_accelerometer.sensor_listener); sensor_listener_stop(g_sensor_info_gyroscope.sensor_listener); sensor_listener_stop(g_sensor_info_pressure.sensor_listener); location_manager_start(g_manager); ecore_timer_thaw(g_timer_id); return; } static void pause_sensors_and_close_sensor_files() { pause_sensors(); sleep(1); close_sensor_files(); return; } static void resume_sensors_and_open_new_sensor_files() { open_new_sensor_files(); resume_sensors(); return; } /** * * @brief Standard service application callbacks. * */ bool service_app_create(void *data) { dlog_print(DLOG_INFO, LOG_TAG, "SensorService created"); return true; } void service_app_terminate(void *data) { dlog_print(DLOG_INFO, LOG_TAG, "SensorService terminated"); pause_sensors_and_close_sensor_files(); sleep(1); // wait 1 second for closing files return; } /** * * @brief Process the restart message sent by the sensor application. * */ static void process_restart_message() { // Validate patient identifier, if wrong set to 000 if(g_personid > 999) { g_personid = 0; dlog_print(DLOG_ERROR, LOG_TAG, "Patient identifier was out of range. Set to %03d", g_personid); } dlog_print(DLOG_INFO, LOG_TAG, "Patient identifier = %03d", g_personid); if( g_service_state == WAITING ) { read_configuration_file(); open_new_sensor_files(); write_configuration_file(); start_sensors(); vibrate(); g_service_state = MEASURING; dlog_print(DLOG_INFO, LOG_TAG, "Measurement started with patient identifier = %03d", g_personid); return; } if( g_service_state == MEASURING ) { // Pause current measurement, start new measurement with new person identifier stop_sensors(); close_sensor_files(); read_configuration_file(); open_new_sensor_files(); write_configuration_file(); start_sensors(); vibrate(); g_service_state = MEASURING; dlog_print(DLOG_INFO, LOG_TAG, "Measurement stopped and restarted with patient identifier = %03d", g_personid); return; } } /** * * @brief Process the clean message sent by the sensor application. * * All sensor- and config files are deleted found in the data folder of the sensor service. * */ static void process_clean_message() { if( g_service_state == MEASURING ) { pause_sensors_and_close_sensor_files(); sleep(1); } char* data_path = NULL; data_path = app_get_data_path(); char linux_command[256]; snprintf(linux_command, 256, "rm %s*aag.dat", data_path); dlog_print(DLOG_INFO, LOG_TAG, "Linux: %s", linux_command); system(linux_command); snprintf(linux_command, 256, "rm %s*bar.dat", data_path); dlog_print(DLOG_INFO, LOG_TAG, "Linux: %s", linux_command); system(linux_command); snprintf(linux_command, 256, "rm %s*con.dat", data_path); dlog_print(DLOG_INFO, LOG_TAG, "Linux: %s", linux_command); system(linux_command); snprintf(linux_command, 256, "rm %s*gps.dat", data_path); dlog_print(DLOG_INFO, LOG_TAG, "Linux: %s", linux_command); system(linux_command); if( g_service_state == MEASURING ) resume_sensors_and_open_new_sensor_files(); vibrate(); dlog_print(DLOG_INFO, LOG_TAG, "Sensor files deleted"); return; } /** * * @brief In the call back service_app_control the message requests are handled. * * @detailed Message which can be received are: * * 1. Launch request to start the sensor service. This message can be ignored. * 2. Restart request with person identifier. This must restart the measurement. * 3. Clean request. This must delete all sensor files. * */ void service_app_control(app_control_h app_control, void *user_data) { // Handle the request of another application. dlog_print(DLOG_INFO, LOG_TAG, "SensorService controlled"); char *operation; char *uri; char *app_id; app_control_get_operation(app_control, &operation); dlog_print(DLOG_INFO, LOG_TAG, "SensorService control requested with operation %s", operation); app_control_get_uri(app_control, &uri); dlog_print(DLOG_INFO, LOG_TAG, "SensorService control requested with uri %s", uri); app_control_get_app_id(app_control, &app_id); dlog_print(DLOG_INFO, LOG_TAG, "SensorService control requested with appid %s", app_id); // Check if the request is restart or clean if (!strcmp(operation, APP_CONTROL_OPERATION_SEND)) { char command[16]; char parameter[16]; sscanf(uri, "%s %s", &command, ¶meter); if(!strcmp("restart", command)) { sscanf(parameter, "%d", &g_personid); process_restart_message(); return; } if(!strcmp("clean", command)) { process_clean_message(); return; } } return; } /** * * @brief Low battery and memory callbacks called by the Tizen framework. * */ static void service_app_low_battery(app_event_info_h event_info, void *user_data) { // APP_EVENT_LOW_BATTERY dlog_print(DLOG_INFO, LOG_TAG, "SensorService low battery"); pause_sensors_and_close_sensor_files(); return; } static void service_app_low_memory(app_event_info_h event_info, void *user_data) { // APP_EVENT_LOW_MEMORY dlog_print(DLOG_INFO, LOG_TAG, "SensorService low memory"); pause_sensors_and_close_sensor_files(); return; } /** * * @brief Check for support of particular sensor. * */ static void check_supported_sensors() { sensor_error_e err = SENSOR_ERROR_NONE; sensorinfo_s sensor_info; err = sensor_get_default_sensor(SENSOR_ACCELEROMETER, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Accelerometer sensor support = %d", err); char *vendor; err = sensor_get_vendor(sensor_info.sensor, &vendor); dlog_print(DLOG_INFO, LOG_TAG, "Accelerometer sensor vendor = %s %d", vendor, err); err = sensor_get_default_sensor(SENSOR_LINEAR_ACCELERATION, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Linear accelerometer sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_GYROSCOPE, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Gyroscope sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_PRESSURE, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Barometer sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_TEMPERATURE, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Temperature sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_MAGNETIC, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Magnetic sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_HUMIDITY, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Humidity sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_PROXIMITY, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Proximity sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_PROXIMITY_NEAR, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Proximity near sensor support = %d", err); err = sensor_get_default_sensor(SENSOR_PROXIMITY_FAR, &sensor_info.sensor); dlog_print(DLOG_INFO, LOG_TAG, "Proximity far sensor support = %d", err); return; } /** * * Start of the sensor service application * */ int main(int argc, char* argv[]) { check_supported_sensors(); char ad[50] = {0,}; service_app_lifecycle_callback_s event_callback; app_event_handler_h handlers[5] = {NULL, }; event_callback.create = service_app_create; event_callback.terminate = service_app_terminate; event_callback.app_control = service_app_control; service_app_add_event_handler(&handlers[APP_EVENT_LOW_BATTERY], APP_EVENT_LOW_BATTERY, service_app_low_battery, &ad); service_app_add_event_handler(&handlers[APP_EVENT_LOW_MEMORY], APP_EVENT_LOW_MEMORY, service_app_low_memory, &ad); return service_app_main(argc, argv, &event_callback, ad); }