GpsPosition

This page has been updated on the 21 July 2010.
Information validated on 2.4.0 framework.

Objective

The purpose of the gpsPosition component is to get GPS position and send it to a server.
This will be done in several steps :

  1. Retrieve the GPS coordinates using the GPS component
  2. End information using the MessageGate component
  3. Manage parameters in the project and change them using the configAccess component
  4. Save information on the device using the PDM component

Preliminary

First you need to create a non graphical component, name it gpsPosition and the package should be com.training.gpsPosition
Then add an Initial class through the JCPN editor.

Get the GPS coordinates

First you have to add the required interfaces in the JCPN file. The interfaces needed here are the Debug and the GPS
Then open the initial file. Here you need to create an instance of each component you are using ( Debud and GPS here), inside the method star() of the initial class.
Here what you should have:

  Component cpn = Component.getInstance();
  Debug dbg = cpn.getDebug();
  Gps 	gps = cpn.getGps();

  dbg.init(0); // initialize debug to see all log
ERROR ? POSSIBLE SOLVE :
	- error on Debug and GPS : add Debug/GPS imports by clicking on the error at the beginning of the file
	- getDebug()/getGps() not available : 
		- save the JCPN file to regenerate the Component class
		- check if you used the Component class of your project (com.training.gpsPosition.Component)																	

Then we need to retrieve the coordinates, to do so you are going to use the GPS class.
To help us using this API, we can use the Java Documentation (2 different ways):

  • in Eclipse interface : go to Help → Help content → Mobile Devices 2.X-Project User Guide → Reference → API Reference → MDI Java Framework
  • in your browser : double click on <sdk_install_dir>\2.X-project\plugins\com.mdi.project.fw.windows_1.0.0\mdi-framework-2.X\docs\index.html

Here you have access to the methods available in this class, for instance methods to manage ephemeris, satellites, antennas, power, feeding, RTC synchronization and position.

To retrieve the GPS coordinates, you are going to use the following method: getPosition(IntRef longitude, IntRef latitude, IntRef speed, IntRef course, boolean wait)

The type “IntRef” is a class from the MDI framework that manages integer values. The type “BoolRef” also exists and let you manage boolean values and byte arrays & strings.
The last parameter is a boolean called wait. This parameter indicates if we wait for the next valid position or just the last value recorded.
By default, if we have GPS reception, the GPS will give us one position per second, so it will have an automatic blocking of a maximum of 1 second.

In this case, we need to create and instantiate a few IntRef and use them as parameters in the method getPosition. So the GPS coordinates will be saved in those variables.
The following lines of code illustrate this:

  IntRef lat = new IntRef();
  IntRef lon = new IntRef();
  IntRef spd = new IntRef();
  IntRef cse = new IntRef

gps.getPosition(lon, lat, spd, course, true); dbg.print("record data : lat="lat.value" , lon="lon.value" , spd="spd.value" , course="+course.value);


if you launch this project in the simulator, you can see a line like this in console box :

[...]
2010-XX-XX XX:XX:XX  L[j$com/gpsPosition$0] record data : lat=4884437 , lon=226362 , spd=8100 , course=18000
[...]
 ERROR ? 
POSSIBLE SOLVE :
	- error on Launching of simulation : 
		- if you have the error "Error while checking "MuiApp.java" in specified main package : XXXX", uncheck the box "Enable user interface Engine" in the simulation configuration.
		- if you have the message "Error(s) or Info(s) found in project : XXXX", you must check the "Problems" box and solve all "Infos" first and all Errors after in order to run your application.

As you can see, we retrieved coordinates, speed, and heading even if you don’t have a GPS inside your computer. Where does this information come from ?
It is because the simulator uses a file which emulates the GPS position, the speed and the heading.
To change this file, you can check the simulator settings in two different ways :
- on the top menu, click on Mobile devices → simulator settings.
- click on the “change simulator settings” icon.
Then you can see the list of all the components of the Framework. And if you click on one of them, you will see the linked parameter.
As an example, if you click on GPS, you will see that you have a “data_source” parameter whose value is set to “1”, which means “File data source”.
Then you have the “file_data_source” parameter with a value of “data/gps/file-1.dat”.
You can view this file in the path …\2.X-project\plugins\com.mdi.project.fw.windows_1.0.0\mdi-framework-2.X\simulator\data\gps\file-1.dat
It is a text file with a list of three NMEA frames (GPRMC, GPGGA and GPGSA). These frames were read and fed into the GPS each second.

You can modify your code like this if you want to see your position changing.

  IntRef lat = new IntRef();
  IntRef lon = new IntRef();
  IntRef spd = new IntRef();
  IntRef cse = new IntRef();

  while (true)
  {
    gps.getPosition(lon, lat, spd, course, true);
    dbg.print("record data : lat="+lat.value+" , lon="+lon.value+" , spd="+spd.value+" , course="+course.value);
  }

. WARNING

  • In this example, we are running a while loop; of course this is for training purposes only, it is better to manage a boolean to stop this loop during the shutdown sequence.
  • When using loops, always watch the memory and CPU usage. (The application may not be able to respond)
    • Create your class outside of the loop if possible
    • Have limitations in your loop (blocking methods or temporisation)

If you recall, it was saying that the start() method could not be a blocking method.
So in this case we need to change the Initial class to run this code as a thread.
To do so, you can make the following changes:

  • in declaration of class
  public class Initial implements com.mdi.tools.cpn.Initial, Runnable {

Solve the error with choice “add unimplemented methods …”.
In the new run() method, you can move the content of start().
And finally add this code in start():

  new Thread(this).start();

Send Data with MessageGate

- Add Required interface MessageGate

We are going to use one of the integrated communication systems of the devices: the MessageGate.

The MessageGate component uses the MDI20 protocol (ASN.1) in order to exchange messages on different channels.
Some channels are reserved for the framework, those channel are between 0 and 5.
Some of the Mobile Devices applications already use MessageGate channels, those channels are between 6 and 40 at this moment.
We recommend customers to use channels greater than 1000, so you can choose a channel between 1000 and 16383.
This component is designed to exchange binary data or text messages between the server and the device.
Each message has a unique ID given by the server, and the sending of messages can be synchronous or asynchronous.
In case of asynchronous messaging, you receive a temporary ID which you need to link with the real ID you receive afterwards.

For this example, we will use the MessageGate in synchronous mode on channel 1234.
If you look at the different methods available in MessageGate, you can see that we have methods to send, receive, manage delays, manage channels and delete waiting messages.
You can see that MessageGate has an init() method which takes parameters; so we must call it right after retrieving the instance:

  Debug dbg = cpn.getDebug();
  Gps 	gps = cpn.getGps();
  MessageGate msg = cpn.getMessageGate();

  dbg.init(0); // initialize debug to see all log
  msg.init(0, 1234); // timeout is unused so we specify channel 1234

You are going to use the send(BuffRef data, int referencedMessageId, int msgTimeout, boolean wait) method.
The first parameter is the data you want to send as a BuffRef, it can contain text or binary data.
The second parameter is the reference message id if you want to answer a previous message.
The third parameter is the time limit of the message in minutes, after this time the message will be deleted.
The fourth parameter is a boolean value which determines if you want to wait for the message acknowledgment (synchronous or asynchronous mode).

You can add this to your code :

  IntRef lat = new IntRef();
  IntRef lon = new IntRef();
  IntRef spd = new IntRef();
  IntRef cse = new IntRef();
  BuffRef buf = new BuffRef("");

  while (true)
  {
    gps.getPosition(lon, lat, spd, course, true);
    buf.clear();
    buf.append("lat="+lat.value+" , lon="+lon.value+" , spd="+spd.value+" , course="+course.value);
    dbg.print("record data : "+buf);
    msg.send(buf, -1, 60, true);
  }

In order to test this we need to set a couple of parameters to connect to the gateway.
In the simulator settings, change the following:
binaryGate.url[ 0 ]=88.191.84.24
binaryGate.port[ 0 ]=4241
modem.force_IMEI=YYYYMMDDHHMMSS

Then you can go on this web site : http://sandbox.g8teway.com/
and enter the following ID :

login : training
password : only4training
client : training

Click on “Start” and go in the “Messages perspective”, then launch the simulator.
If all your parameters are correct, then you will see new entries at the end of the message list on the web site.
And in the console window, you will see something like this :

[...]
2010-XX-XX XX:XX:XX  L[j$com/gpsPosition$0] record data : lat=4884473 , lon=226385 , spd=10799 , course=27000
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl:send: begin
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl[2]:send: waiting send
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl:send: call _binaryGateManager
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager:send: SEND data (len=73)
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager[2]:send: data packet sent
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl[2]:send: yield done
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager.readPacket: 14 bytes read, packet size is 14
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl:run: wait for packet ...
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager.readPacket: wait for packet ...
[...]

Close the simulator.
As you can see, sending a message is quite easy using the messageGate. Now what about receiving messages ?

We will now use the getMessage(BuffRef data, IntRef timestamp) method.
The first parameter is the received message if there is one.
The second parameter is the message timestamp.
The method returns an integer representing the message status: 0 if no new messages, a positive message id on reception, < 0 if an error occured.

So we only have to modify the code as follows to retrieve a message from the server:

  BuffRef buf = new BuffRef("");
  BuffRef recv = new BuffRef(1024);
  IntRef time = new IntRef();
  
  while (true)
  {
    gps.getPosition(lon, lat, spd, course, true);
    buf.clear();
    buf.append("lat="+lat.value+" , lon="+lon.value+" , spd="+spd.value+" , course="+course.value);
    dbg.print("record data : "+buf);
    msg.send(buf, -1, 60, true);
    int ret = msg.getMessage(recv, time);
    if (ret > 0)
    {
      dbg.debug("Message receive ["+time.value+"] : "+recv);
    }
  }

Launch the simulator.
On the “Messages perspective” click on new, and set the number of your device, channel 1234, choose instantaneous message and type a text like “do you receive this, gpsPosition ?”
Send the message and watch the SDK console, it should look like this:

[...]
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager.readPacket: wait for packet ...
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager.readPacket: 53 bytes read, packet size is 53
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl:run: wait for packet ...
[...]
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl:send: begin
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl[1]:send: waiting send
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl:send: call _binaryGateManager send with timeout 0
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager:send: SEND data (len=7)
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] BinaryGate_impl[1]:send: yield done
2010-XX-XX XX:XX:XX  L[j$com/mdi/services/binaryGate$0] MDI20BinaryGateManager[0]:send: data packet sent
2010-XX-XX XX:XX:XX  L[j$com/gpsPosition$0] receive msg : do you receive this, gpsPosition ?
[...]

Close the simulator.

To conclude, managing messages is an easy task using the MessageGate.
You can also take a look at the documentation about the server to see how to retrieve and send data using our server.

Manage configuration parameters

For the moment we use raw parameters set in the source code to manage the channel.
That means that if you have a problem because the same channel is used by another application, you need to change, re-package, and update your component.
To avoid this, you can manage parameters.

  • open the JCPN file.
  • Add Required interface ConfigAccess.
  • Click on parameters, then add.
  • Click on new parameters and change the name for channel and type to int.

After that you just have to change your code as follows to integrate this parameter to your code:

  msg.init(0, _cpn.param_channel()); // timeout is unused; use parameter channel to set channel

The advantage of using channels is that parameters can be changed in several ways:

  • SMS with the set command (see C4 documentation : SMS section for more information).
  • Update by sending a config.txt file.
  • Console using config.txt file or “s” command.
  • AdminProtocol which uses a specific channel of the MessageGate and JSON message to get and set parameters.
  • API using configAccess components in your code.

We are going to see how to use the ConfigAccess component to change the channel parameter when we receive a specific message.
Take a look at the different methods available in ConfigAccess, you can see that we have methods to set, get, create and insert a parameter.
You can see that ConfigAccess has an init() method which takes a parameter, so we must call it right after retrieving the instance.
This parameter is the component we want to change as a BuffRef.
After that, if we want to change a parameter, we need to retrieve the key linked to this parameter: we will use the getKey(String parameter) method with the parameter name.

  MessageGate msg = cpn.getMessageGate();
  ConfigAccess cfg = cpn.getConfigAccess();
		
  cfg.init(new BuffRef("gpsPosition")); // to tell ConfigAccess we want to change gpsPosition parameter
  int paramKey = cfg.getKey("channel");
  dbg.init(0); // initialize debug to see all log

We will change the parameter between 1234 and 1235 when we receive a “SWITCH” message on our channel for exemple.
To do this we need to modify the source code as follows:

  int ret = msg.getMessage(recv, time);
  if (ret > 0)
  {
    if (recv.toString().equals("SWITCH"))
    {
      // changing parameter using configAcces
      dbg.print("SWITCH PORT");
      if (Component.getInstance().param_channel() == 1234)
        cfg.set(paramKey, "1235", "", "");	// change parameter with index paramKey , new value 1235, don't use table and change current configuration
      else
        cfg.set(paramKey, "1234", "", ""); 	// change parameter with index paramKey , new value 1234, don't use table and change current configuration
      msg.setChannel(Component.getInstance().param_channel());
    }
    else
      dbg.error("receive msg : "+recv);
  }

And then you can start the simulator and see that the position comes from the application.
You can send a message to your application using SWITCH inside and after that you will see that the position came from another port.

Manage Saving of Data

One of the last problem in this application is that if we are not connected, all data will be lost.
So it can be important to store it and try to send it when the connection comes up.

The PDM component allows you to manage different PDMs (persistent data manager).
Each PDM is defined by a name and a size, and it is a FIFO circular buffer.
The maximum number of PDMs available is 512 and each PDM can have a maximum size of 3 MB.

We are going to change our project a little:

  • The Initial class will become a recorder in which we can retrieve GPS coordinates, print them and save them in the PDM.
  • The new Emitter class will become a emitter thread in which we can retrieve PDM data, try to send it and depending on the result, manage a PDM.

Create a new Emitter class and copy inside all about connections and management of parameters.
And in the Initial class just keep the retrieving of coordinates with GPS, printing of data with Debug.

After that we just need to manage the PDM between the two classes.
Take a look at the different methods available in PDM, you can see that we have methods to create, delete, manage PDMs and methods to add, get, and remove data.

In the Initial class we need to create the PDM and add data inside, we will need to use the getKey() and addData() methods.

  Gps gps  = Component.getInstance().getGps();
  Debug dbg = Component.getInstance().getDebug();
  Pdm pdm = Component.getInstance().getPdm();

  dbg.init(0);
  int pdmKey = pdm.getKey("GPTraining01", true, 1024); // retreave pdm called "GPTraining01" and if it doesn't exist create it with size 1024
		
  IntRef lat = new IntRef();
  IntRef lon = new IntRef();
  IntRef spd = new IntRef();
  IntRef course = new IntRef();
  BuffRef buf = new BuffRef("");
		
  while (true)
  {
    gps.getPosition(lon, lat, spd, course, true);
    buf.clear();
    buf.append("lat="+lat.value+" , lon="+lon.value+" , spd="+spd.value+" , course="+course.value);
    dbg.print("record data : "+buf);
    pdm.addData(pdmKey, buf, ',', false); // add "buf" data on pdm linked with pdmKey with separator ',' and don't erase pdm before addings.
  }

The next step is to manage the Emitter, it is a bit more difficult.
First you need to retrieve the data. If this data is good you must try to send it to the MessageGate.
If the data was sent succesfuly we can delete it from the PDM, if it wasn’t successful we must go back to the previous element in order to continue to try sending with the same value on the next iteration. To do so you will need to use the getKey(), getData(), remData() and cancelLastGet() methods.
Try to do it before taking a look at the solution.
You can test this in the simulator by switching on or off your connection, normally you will receive all of your packages.

The code you need to add is:

  [...]
  Pdm pdm = Component.getInstance().getPdm();

  int pdmKey = pdm.getKey("GPTraining01", true, 1024); // retreave pdm called "GPTraining01" and if it don't exist create it with size 1024
  [...]
  while (true)
  {
    int ret = pdm.getData(pdmKey, recv, ',', true, true); // get the data inside of pdm and increment pdm reading pointer
    if (ret <= 0) // if error wait before trying again
    {
      try {Thread.sleep(1000);} 
      catch (InterruptedException e) {e.printStackTrace();}
      continue;
    }

    ret = msg.send(recv, -1, -1, true);
    if (ret < 0)
    {
      dbg.error("error on sending ("+ret+")");
      pdm.cancelLastGet(pdmKey, false);		// if error go back on previous element (decrement pdm reading pointer)
    }
    else
    {
      pdm.remData(pdmKey, false);		// succes, delete data sent from pdm
    }
    [...]