To perform basic triangulation of the client GSM device’s location, the GSM modem can be queried to return information about the towers in the area, including their unique Cell Ids, and the signal strength obtained from each.

Using this information, a circle can be drawn around each cell, with the circle size relative to the signal strength of each tower. The point where these circles intersect can be considered the client’s probable location.

Here we explore methods of communication with an internal GSM modem over a serial interface, some of the applicable Hayes or AT commands used to communicate with the hardware and how the information provided to us buy the modem can be used to determine the device’s location by triangulating it’s position against known cell tower locations.

Contents

Setting up your test environment

Verifying Modem Configuration

The first order of business is to verify our serial port configuration using the stty command. In my case, the gsm modem is available on /dev/ttyS1

root@ion:~# stty -F /dev/ttyS1
speed 9600 baud; line = 0;
susp = <undef>; min = 1; time = 0;
-brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok noflsh -echoctl -echoke

To verify that the GSM modem is functioning correctly, the gsmctl command can be used. If your sim requires a pin, the folowing message may be displayed:

root@ion:~# gsmctl all
gsmctl[ERROR]: ME/TA error 'SIM PIN required' (code 311)

If that is the case, you can provide the pin using gsmctl -I as follows: (the same can be achieved via the use of the +CPIN Hayes command)

root@ion:~# gsmctl -I "+cpin=NNNN"

If all is well the result of calling gsmctl all should look something like this:

root@ion:~# gsmctl all
<me0> Manufacturer: Telit
<me1> Model: GC864-QUAD
<me2> Revision: 10.00.063
<me3> Serial Number: 359294031006030
<fun> Functionality Level: 1
...

gsmctl requires a symlink at /dev/mobilephone that points to your GSM modem’s serial interface. If the symlink does not exist on your system you can create it by issuing the command:
ln -s /dev/ttyS&lt;#&gt; /dev/mobilephone

Listening on Your Modem’s Serial Interface

Next we need to establish a method of communication with the modem. In a Linux terminal, listening on a serial interface is as simple as issuing the following command:
cat /dev/mobilephone &amp;

This prints any communication received from the device at /dev/mobilephone to our terminal in real time.

If like me you find yourself accessing a device via ssh or creating a serial terminal from a remote device this solution for listening on the modem’s serial interface may not always be appropriate as you will need to be able to use the same terminal for issuing commands to the modem and viewing these responses.

In this case, the best method for separating the responses from the modem out while testing is to redirect the output from the modem’s serial interface into a text file in a background process:
cat /dev/mobilephone &gt; modem.log &amp;

Now your terminal will be left free for you to issue commands to the modem and the responses will be stored in the modem.log file for you to view when required.

Communicating with your Modem

A set of commands, known as Hayes or AT commands have been established as a standard for issuing commands to modems. Commands begin with the character sequence AT for “Attention” indicating to the modem that a command follows, the command, and are then terminated by a carriage return (‘\r’ or 0x0D).

root@ion:~# echo -e "ATZ\r" &gt; /dev/mobilephone
root@ion:~# cat modem.log
ATZ

OK


The Hayes command, ATZ instructs the modem to perform a soft reset. The echo command is used with the -e option to enable interpretation of backslash escaped characters. The modem.log file contains our command, and the modem’s response, “OK”.

+CREG? – Obtaining Network Registration Information

By querying the modems current network registration status we can determine whether or not the modem is registered on the network, whether or not the sim is registered as roaming or on it’s home network and on which specific cell (tower) the sim is registered.

To get this information, one must first issue the command AT+CREG=2 to enable unsolicited network registration information with network Cell identification data. With this option set, the new network registration status and network Cell identification data will be sent by the modem every time it changes. It also allows us to query the information as necessary. the options are:

  1. Disable network registration unsolicited result code (factory default)
  2. Enable network registration unsolicited result code
  3. Enable network registration unsolicited result code with network cell identification data
root@ion:~# echo -e "AT+CREG=2\r" &gt; /dev/mobilephone
root@ion:~# echo -e "AT+CREG?\r" &gt; /dev/mobilephone
root@ion:~# cat modem.log
AT+CREG=2

OK
AT+CREG?

+CREG: 2,1,"02D0","CB9B"

OK

Understanding the Network Registration Report Results

After issuing the AT+CREG=2 command, we can use AT+CREG? to query the Network Registration Report. The modem returned +CREG to identify the report, 2 indicating the current Network Registration Report Mode and 1 indicating the current network registration status as registered on the “home” network. This is followed by the Local Area Code and Cell Id for the Cell on which the sim is currently registered in hex.

+CREG: <stat>[<Lac>,<Ci>]
<stat>

  1. Not registered, not currently searching for a new operator to register to
  2. Registered, home network
  3. Not registered, but currently searching for a new operator to register to
  4. Registration denied
  5. Unknown
  6. Registered, roaming

<Lac> – Local Area Code for the current cell (hex)
<Ci> – Cell Id for the current cell (hex)

As we have enabled unsolicited network registration information, this information will continue to be provided over the serial interface when it changes until the Network Registration Report mode is set back to 0.

#MONI – Obtaining GSM information for neighboring cells using Cell Monitor

If we’re going to triangulate our position, we’re going to need more information about the cells in our area. In order to get the location of a cell from OpenCellID for example, we’ll need:

  • Mobile Country Code
  • Mobile Network Code
  • Location Area Code
  • Cell ID

To get an accurate location using the Google Maps Geolocation API, or to calculate it yourself using your own database of cell coordinates you will also likely need:

  • Age
  • Signal Strength
  • Timing Advance

To obtain this information, I have made use of the #MONI (Cell Monitor) AT command. This is not a command that was specified in the Hayes or 3GPP standards documents and may not be available on your modem.

Obtaining the modem manufacturer name (+GMI) and model number (+GMM)

The #MONI command is however available for most modern Telit devices. If it is not available on your device I would recommend determining the manufacturer and model of your device and consulting the manufacturer’s AT command reference guide for an equivalent command. the following standard Hayes commands can be used to obtain manufacturer and model information:

root@ion:~# cat /dev/null &gt; modem.log
root@ion:~# echo -e "AT+GMI\r" &gt; /dev/mobilephone
root@ion:~# echo -e "AT+GMM\r" &gt; /dev/mobilephone
root@ion:~# cat modem.log
AT+GMI

Telit

OK
AT+GMM

GC864-QUAD-V2

OK

Obtaining neighbor cell GSM information

The #MONI command can be used to extract GSM related information from the serving cell, and up to six neighbouring cells. The set command can be used to select the cell from which to request information: echo -e "AT#MONI=0\r" &gt; /dev/mobilephone

The parameter in this command can be the cell number identified by the ordinal number 0 (the serving cell) to 6 (the neighbouring cells). Below, I have cleared my log file, selected the serving cell and requested the information:

root@ion:~# cat /dev/null &gt; modem.log
root@ion:~# echo -e "AT#MONI=0\r" &gt; /dev/mobilephone
root@ion:~# echo -e "AT#MONI?\r" &gt; /dev/mobilephone
root@ion:~# cat modem.log
AT#MONI=0

OK
AT#MONI

#MONI: VodaCom-SA BSIC:73 RxQual:0 LAC:02D0 Id:CB9B ARFCN:31 PWR:-83dbm TA:1

OK

When extracting data for the serving cell and the network name is known the format is:#MONI: <netname> BSIC:<bsic> RxQual:<qual> LAC:<lac> Id:<id> ARFCN:<arfcn> PWR:<dBm> dBm TA: <timadv>

When the network name is unknown, the format is:#MONI: <cc> <nc> BSIC:<bsic> RxQual:<qual> LAC:<lac> Id:<id> ARFCN:<arfcn> PWR:<dBm> dBm TA: <timadv>

  • <netname> – name of network operator
  • <cc> – country code
  • <nc> – network operator code
  • <n> – progressive number of adjacent cell
  • <bsic> – base station identification code
  • <qual> – quality of reception 0..7
  • <lac> – localization area code
  • <id> – cell identifier
  • <arfcn> – assigned radio channel
  • <dBm> – received signal strength in dBm
  • <timadv> – timing advance

In the next example, I have cleared my log file, selected the first neighbouring cell and requested the gsm information:

root@ion:~# cat /dev/null &gt; modem.log
root@ion:~# echo -e "AT#MONI=1\r" &gt; /dev/mobilephone
root@ion:~# echo -e "AT#MONI\r" &gt; /dev/mobilephone
root@ion:~# cat modem.log
AT#MONI=1

OK
AT#MONI

#MONI: Adj Cell 1 LAC:02D0 Id:CC61 ARFCN:39 PWR:-82dbm

OK

When extracting data for an adjacent cell, the format is:#MONI: Adj Cell<n> [LAC:<lac> Id:<id>] ARFCN:<arfcn> PWR:<dBm> dBm

Finally, if the Cell Monitor mode is set to 7, GSM-related information from the whole set of seven cells, including the serving cell and it’s neighbour list is returned in tabular format.

root@ion:~# cat /dev/null &gt; modem.log
root@ion:~# echo -e "AT#MONI=7\r" &gt; /dev/mobilephone
root@ion:~# echo -e "AT#MONI\r" &gt; /dev/mobilephone
root@ion:~# cat modem.log
AT#MONI=7

OK
AT#MONI

#MONI: Cell BSIC LAC CellId ARFCN Power C1 C2 TA RxQual PLMN
#MONI: S 73 02D0 CB9B 31 -82dbm 20 20 1 0 VodaCom-SA
#MONI: N1 03 02D0 CC61 39 -85dbm 17 13
#MONI: N2 53 02D0 CB99 50 -90dbm 12 8
#MONI: N3 13 02D0 CC63 37 -91dbm 11 7
#MONI: N4 16 02D0 CB9A 38 -93dbm 9 5
#MONI: N5 22 02D0 D23C 41 -94dbm 8 4
#MONI: N6 35 02D0 0000 44 -99dbm 3 -1

OK
  • <C1value> – C1 reselection parameter
  • <C2value> – C2 reselection parameter

Triangulating your position using GSM Cell Tower Locations

Cell LocationsWith this information in hand, and provided that you have access to a database of coordinates for the Cell IDs, triangulation of the our is now possible. The exact location’s of these cells is not generally freely available, however, if a deal cannot be struck with the network operators in your region and you cannot afford to make use of the Google Maps Geolocation API, a number of services exist for obtaining coordinates for cell towers using this information including OpenCellID.

The following coordinates for our towers were obtained from OpenCellID.

These open databases are often generated by grabbing the current GPS location of a mobile device and then allocating that location to all cells in range, and so are wildly inaccurate. In fact 3 of these towers appear to have been placed on top of each other in the middle of a shopping mall.

However this will have to suffice if you do not have the resources to obtain access to database of actual cell positions.

Cell Latitude Longitude
CB9B -25.868423 28.189260450000006
CC61 -25.8616442888889 28.195777477777824
CB99 -25.8663650111111 28.19577164444445
CB9A -25.8583868203475 28.189013391625167
CC62 -25.8583868203475 28.1890133916252
D23C -25.8583868203475 28.1890133916252
CC63 -25.87912 28.18255

Triangulation Algorithm

I have used JavaScript while experimenting with this for ease of use when plotting the results on Google Maps.

To perform basic triangulation of the client devices location, a circle is drawn around each cell, with the circle size relative to the signal strength of each tower.
The point where these circles intersect can be considered the client’s probable location. Obviously dBm is not a measure of distance and so determining the actual circle radius is an exercise in calculating the signal strength’s relative weights, and growing the circles around their point of origin according to that ratio until the circles intersect.

So, where our cell tower’s x & y coordinates are represented as:

(function () {
var tx1 = 28.189260450000006;
var tx2 = 28.195777477777824;
var tx3 = 28.19577164444445;
var tx4 = 28.189013391625167;
var tx5 = 28.1890133916252;
var tx6 = 28.1890133916252;
var tx7 = 28.18255;

var ty1 = -25.868423;
var ty2 = -25.8616442888889;
var ty3 = -25.8663650111111;
var ty4 = -25.8583868203475;
var ty5 = -25.8583868203475;
var ty6 = -25.8583868203475;
var ty7 = -25.87912;

...

…and the signal strength for each tower is expressed as:

...

var s1 = -82;
var s2 = -85;
var s3 = -90;
var s4 = -91;
var s5 = -93;
var s6 = -94;
var s7 = -99;

...

The signal strength ratio for each tower can be calculated as ratio = signal strength/(total, combined signal strength):

...

var sr1 = s1 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr2 = s2 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr3 = s3 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr4 = s4 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr5 = s5 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr6 = s6 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr7 = s7 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);

...

Finally, the client device’s x & y coordinates can be calculated individually by multiplying each tower’s coordinate by it’s signal strength ratio and adding them together as follows:

...

var longitude = ((tx1 * sr1) + (tx2 * sr2) + (tx3 * sr3) + (tx4 * sr4) + (tx5 * sr5) + (tx6 * sr6) + (tx7 * sr7));
var latitude = ((ty1 * sr1) + (ty2 * sr2) + (ty3 * sr3) + (ty4 * sr4) + (ty5 * sr5) + (ty6 * sr6) + (ty7 * sr7));

console.log("longitude: " + longitude);
console.log("latitude: " + latitude);
})();

Triangulation result

Triangulated Location

Full Triangulation Algorithm (JavaScript)

(function () {
var tx1 = 28.189260450000006;
var tx2 = 28.195777477777824;
var tx3 = 28.19577164444445;
var tx4 = 28.189013391625167;
var tx5 = 28.1890133916252;
var tx6 = 28.1890133916252;
var tx7 = 28.18255;

var ty1 = -25.868423;
var ty2 = -25.8616442888889;
var ty3 = -25.8663650111111;
var ty4 = -25.8583868203475;
var ty5 = -25.8583868203475;
var ty6 = -25.8583868203475;
var ty7 = -25.87912;

var s1 = -82;
var s2 = -85;
var s3 = -90;
var s4 = -91;
var s5 = -93;
var s6 = -94;
var s7 = -99;

var sr1 = s1 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr2 = s2 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr3 = s3 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr4 = s4 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr5 = s5 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr6 = s6 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);
var sr7 = s7 / (s1 + s2 + s3 + s4 + s5 + s6 + s7);

var longitude = ((tx1 * sr1) + (tx2 * sr2) + (tx3 * sr3) + (tx4 * sr4) + (tx5 * sr5) + (tx6 * sr6) + (tx7 * sr7));
var latitude = ((ty1 * sr1) + (ty2 * sr2) + (ty3 * sr3) + (ty4 * sr4) + (ty5 * sr5) + (ty6 * sr6) + (ty7 * sr7));

console.log("longitude: " + longitude);
console.log("latitude: " + latitude);
})();

Or rather:

(function () {

var towers = [
{
latitude: -25.868423,
longitude: 28.189260450000006,
signalStrength: -82,
signalStrengthRatio: 1
},
{
latitude: -25.8616442888889,
longitude: 28.195777477777824,
signalStrength: -85,
signalStrengthRatio: 1
},
{
latitude: -25.8663650111111,
longitude: 28.19577164444445,
signalStrength: -90,
signalStrengthRatio: 1
},
{
latitude: -25.8583868203475,
longitude: 28.189013391625167,
signalStrength: -91,
signalStrengthRatio: 1
},
{
latitude: -25.8583868203475,
longitude: 28.1890133916252,
signalStrength: -93,
signalStrengthRatio: 1
},
{
latitude: -25.8583868203475,
longitude: 28.1890133916252,
signalStrength: -94,
signalStrengthRatio: 1
},
{
latitude: -25.87912,
longitude: 28.18255,
signalStrength: -99,
signalStrengthRatio: 1
}
];

var totalSignalStrength = 0;
for (var i = 0; i &lt; towers.length; i++)
totalSignalStrength += towers[i].signalStrength;

for (var i = 0; i &lt; towers.length; i++)
towers[i].signalStrengthRatio = towers[i].signalStrength / totalSignalStrength;

var clientLongitude = 0;
for (var i = 0; i &lt; towers.length; i++)
clientLongitude += towers[i].longitude * towers[i].signalStrengthRatio;

var clientLatitude = 0;
for (var i = 0; i &lt; towers.length; i++)
clientLatitude += towers[i].latitude * towers[i].signalStrengthRatio;

console.log("longitude: " + clientLongitude);
console.log("latitude: " + clientLatitude);
})();