Gps service Sample Overview

Mobile native

The GPS Service sample demonstrates how to implement a Tizen service that provides geolocation data. The geolocation data is obtained using the Location API and broadcasted to the consumer using the Message Port API.

Using the GPS module, the following information is obtained and broadcasted:

  • Longitude
  • Latitude
  • Satellite information (the number of satellites in view)

There are 2 types of messages broadcasted through the message port:

  • Position update contains the current longitude and latitude. This message is sent at 1-second intervals.
  • Satellite update contains the number of satellites currently in view. This message is sent at 5-second intervals.

No user interface is provided, as this is a service-like application that runs in the background.

The current implementation requires precise knowledge about the message receiver application (its package name).

Prerequisites

  • To ensure proper application execution, the following privilege must be set:

    • http://tizen.org/privilege/location
  • To ensure proper application execution, the following features must be enabled:

    • http://tizen.org/feature/location
    • http://tizen.org/feature/location.gps

Implementation

Location Manager Module

The application uses the location manager to retrieve the GPS and satellite data.

The location_manager_create() function is used to initialize the location manager handle, which is later used to acquire geolocation data. The first parameter defines the method used to retrieve this information (the application uses the Global Positioning System (GPS) method). The second parameter is the newly created location manager handle, used by every other location manager function.

// Create the location manager handle
if (location_manager_create(LOCATIONS_METHOD_GPS, &s_geolocation_data.manager) != LOCATIONS_ERROR_NONE)
{
   dlog_print(DLOG_ERROR, LOG_TAG, "Failed to create location manager");
	
   return false;
}

User-defined callback functions are registered to be invoked at defined intervals with the updated position information. The callbacks are defined for a position update with the POSITION_UPDATE_INTERVAL interval (1 second) and a satellite data update with the SATELLITE_UPDATE_INTERVAL interval (5 seconds).

// Register callbacks for position and satellites data update
location_error_e pos_cb = location_manager_set_position_updated_cb(s_geolocation_data.manager, _geolocation_manager_position_updated_cb, POSITION_UPDATE_INTERVAL, NULL);
location_error_e sat_cb = gps_status_set_satellite_updated_cb(s_geolocation_data.manager, _geolocation_manager_satellite_updated_cb, SATELLITE_UPDATE_INTERVAL, NULL);

if (pos_cb != LOCATIONS_ERROR_NONE || sat_cb != LOCATIONS_ERROR_NONE)
{
   dlog_print(DLOG_ERROR, LOG_TAG, "Failed to register callbacks for location manager");
   geolocation_manager_service_destroy();

   return false;
}

To manage the location service:

  1. To start the location service, the location_manager_start() function is invoked:

    location_manager_start(s_geolocation_data.manager);
    
  2. To pause (stop) the location service, the location_manager_stop() function is invoked:

    location_manager_stop(s_geolocation_data.manager);
    
  3. When the location service is no longer needed, it is destroyed. The callbacks for position and satellite data updates must be unset and the location manager must be stopped. After that, the location_manager_destroy() function must be used to release the location manager.

    location_manager_unset_position_updated_cb(s_geolocation_data.manager);
    gps_status_unset_satellite_updated_cb(s_geolocation_data.manager);
    location_manager_stop(s_geolocation_data.manager);
    location_manager_destroy(s_geolocation_data.manager);
    

Once location service is up and running, the application acquires initial position and satellite data.

The location_manager_get_last_position() function is used to retrieve the location data. The data provided by the function is the last recorded coordinates. In addition to the position data (longitude, latitude, and altitude), the function also provides a timestamp that can be used to determine whether the acquired data is still valid.

// Get last location information
if (location_manager_get_last_position(s_geolocation_data.manager, &altitude, &init_coords->latitude, &init_coords->longitude, &timestamp) != LOCATIONS_ERROR_NONE)
{
   dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get last location");
	
   return false;
}

The gps_status_get_satellite() function is used to acquire the satellite data. The second and third parameters are the number of active satellites and the number of satellites in view, respectively. This function also provides a timestamp for determining the data validity.

Note that the satellite data is not supported on the Tizen Emulator. On the Emulator, this function always returns the LOCATIONS_ERROR_NOT_SUPPORTED error code. To ensure that the GPS service application works on the Emulator in case of the gps_status_get_satellite() failure, the satellite count is set to a fixed value (0).

// Get initial count of satellites
location_error_e ret = gps_status_get_satellite(s_geolocation_data.manager, &num_of_active, satellites_count, &timestamp);
if (ret != LOCATIONS_ERROR_NONE)
{
   dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get satellite data [%d]", ret);
   // Satellite data is not supported on Tizen Emulator - satellite count remains at 0
}

The _geolocation_manager_position_updated_cb() callback function is registered to be invoked at defined intervals with the updated position information. As parameters, this function gets the new latitude, longitude, and altitude, as well as a timestamp of the measurement and user-defined data.

To ensure a proper information flow, the position update message is sent only if the initial data has already been sent. To prevent the application from sending useless information, it sends the position update only if the measurement took place less than MAX_TIME_DIFF (15) seconds ago. If these conditions are met, the application invokes the _geolocation_manager_position_coords_send() function to send new coordinates to the GPS consumer application.

static void
_geolocation_manager_position_updated_cb(double latitude, double longitude, double altitude, time_t timestamp, void *data)
{
   location_coords_s coords;

   coords.latitude = latitude;
   coords.longitude = longitude;

   time_t curr_timestamp;

   // Get current time to compare to the last position timestamp
   time(&curr_timestamp);


   // Send updated position only if init data has been sent and position update
   // was registered less than MAX_TIME_DIFF seconds ago
   if (s_geolocation_data.init_data_sent && curr_timestamp - timestamp < MAX_TIME_DIFF)
   {
      // Send position update through message port
      if (_geolocation_manager_position_coords_send(coords))
      {
         dlog_print(DLOG_INFO, LOG_TAG, "Position updated to %f, %f", latitude, longitude);
      }
      else
      {
         dlog_print(DLOG_ERROR, LOG_TAG, "Failed to send position update");
      }
   }
}

The _geolocation_manager_satellite_updated_cb() callback function is registered to be invoked at defined intervals with the updated satellite information (number of active and in view satellites). Also, the timestamp of the measurement is provided. A proper information flow is ensured in the same way as in the position update callback described above.

If the initial data has been sent, new satellites in view count is sent to the GPS consumer application using the _geolocation_manager_satellites_count_send() function.

static void
_geolocation_manager_satellite_updated_cb(int num_of_active, int num_of_inview, time_t timestamp, void *data)
{
   // Send update satellite count only if init data has been sent
   if (s_geolocation_data.init_data_sent)
   {
      // Send satellite count update through message port
      if (_geolocation_manager_satellites_count_send(num_of_inview))
      {
         dlog_print(DLOG_INFO, LOG_TAG, "Satellite count updated: inview %d", num_of_inview);
      }
      else
      {
         dlog_print(DLOG_ERROR, LOG_TAG, "Failed to send satellite count update");
      }
   }
}

Message Port Module

The message port module is used for communication between the GPS service and consumer applications. The GPS consumer application registers a local port that can be used by other applications to send data to, provided that they have its package and port name. For that reason, the port name and GPS consumer application name are fixed.

The GPS service application can check the state of the message port that the GPS consumer application has registered using the message_port_check_remote_port() function. This function takes the remote app ID and remote port name as parameters.

// Check remote port state from GPS consumer
if (message_port_check_remote_port(REMOTE_APP_ID, REMOTE_PORT, &exists) != MESSAGE_PORT_ERROR_NONE)
{
   dlog_print(DLOG_ERROR, LOG_TAG, "Failed to check remote port");
   geolocation_manager_service_destroy();
	
   return false;
}

if (!exists)
{
   dlog_print(DLOG_ERROR, LOG_TAG, "Remote port is not registered");
}

The initial data is sent using the _geolocation_manager_init_data_send() function. If it fails (because the remote port is not registered or some internal error occurs), the service continues to try to send the data at SEND_DATA_INTERVAL (5-second) intervals.

// Send initial data to GPS consumer port
if (!_geolocation_manager_init_data_send())
{
   dlog_print(DLOG_ERROR, LOG_TAG, "Failed to send init data - create timer to periodically try to send data");
   ecore_timer_add(SEND_DATA_INTERVAL, _geolocation_manager_init_data_send_cb, NULL);
}

The position update message is created in the _geolocation_manager_position_coords_send() function. It uses the Bundle API to form a message, which consists of 3 elements: message type, and the latitude and longitude value. After the message is formed, the _send_message() function is invoked to send it.

static bool
_geolocation_manager_position_coords_send(location_coords_s coords)
{
   bundle *b = bundle_create();
   char latitude_str[CHAR_BUFF_SIZE], longitude_str[CHAR_BUFF_SIZE];

   if (!b) 
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "Failed to create bundle, the coords will not be sent");
		
      return false;
   }

   snprintf(latitude_str, CHAR_BUFF_SIZE, "%f", coords.latitude);
   snprintf(longitude_str, CHAR_BUFF_SIZE, "%f", coords.longitude);

   bundle_add_str(b, "msg_type", MESSAGE_TYPE_POSITION_UPDATE);
   bundle_add_str(b, "latitude", latitude_str);
   bundle_add_str(b, "longitude", longitude_str);

   bool ret = _send_message(b);

   bundle_free(b);

   return ret;
}

The satellite update message is created in the _geolocation_manager_satellites_count_send() function, using the Bundle API. It consists of 2 strings: message type and satellite count. The created bundle is sent to the GPS consumer using the _send_message() function.

static bool
_geolocation_manager_satellites_count_send(int s_count)
{
   bundle *b = bundle_create();
   char count_str[CHAR_BUFF_SIZE];

   if (!b) 
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "Failed to create bundle, the satellites will not be sent");
		
      return false;
   }

   snprintf(count_str, CHAR_BUFF_SIZE, "%d", s_count);

   bundle_add_str(b, "msg_type", MESSAGE_TYPE_SATELLITES_UPDATE);
   bundle_add_str(b, "satellites_count", count_str);

   bool ret = _send_message(b);

   bundle_free(b);

   return ret;
}

The _send_message() function is responsible for sending the message passed as the parameter to the message port of the remote application. To do that, the message_port_send_message() function must be invoked. The remote app ID and remote port name have defined fixed values.

static bool
_send_message(bundle *b)
{
   if (!b) 
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "Cannot send message, the bundle is NULL");
		
      return false;
   }

   // Send message to specified remote port
   int ret = message_port_send_message(REMOTE_APP_ID, REMOTE_PORT, b);

   if (ret != MESSAGE_PORT_ERROR_NONE)
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "Failed to send message: error %d", ret);
		
      return false;
   }

   return true;
}