In this post I show how the Wolfram Language enables us to build a geolocation device with a Rasberry Pi and the GSM module SIM908 (see this previous post for futher details about this module ). The module SIM908 includes a GPS and it is sold to develop geolocation applications. Usually this development involves a fair amount of code in other traditional languages and in this example I show how the Wolfram Language makes it possible to program a geolocator in fewer lines of code. The way in which the geolocator works is that it receives a SMS with a password string and after checking that the password is correct, sends a SMS to another cell phone with the geographic coordinates of the place where the geolocator is.
The code shown in this post can be adapted to other situations such as the remote control of a R-Pi or devices connected to it. The only requirement is that the R-Pi is placed in a region with cellular network coverage.
Material:
- A R-Pi computer (I used model B+).
- SIM908 GSM module available from a number of vendors (I only tested the module sold here).
- A working SIM card of a cell phone.
Description
The set up of the GSM module is the same as in this post and we assume that you are familiar with the explanations given there about how the module is accessed and controlled using the Wolfram Language. The difference is that now we shall control the module by means of a program run as a script. The complete code of this program is the following
(* Open serial port *)
serial = DeviceOpen["Serial", {"/dev/ttyAMA0", "BaudRate" -> 115200}];
(* Unlock SIM card by sending its PIN code *)
DeviceWrite[serial, "AT+CPIN=****\r"];
(* Switch GPS on *)
DeviceWrite[serial, "AT+CGPSPWR=1\r"];
(* Reset GPS *)
DeviceWrite[serial, "AT+CGPSRST=0\r"];
(* Set SMS mode to text *)
DeviceWrite[serial, "AT+CMGF=1\r"];
(* Get location from GPS *)
Location[] :=
Module[{nmea, lat, long},
DeviceWrite[serial, "AT+CGPSINF=0\r"];
Pause[5];
nmea = ImportString[FromCharacterCode@DeviceReadBuffer[serial], "Table"];
nmea = nmea[[-2]];
nmea = First@StringSplit[nmea, ","];
long = ToExpression@nmea[[2]];
lat = ToExpression@nmea[[3]];
(* Transform to decimal lat long *)
lat = IntegerPart[lat/100] + 100/60 (lat/100 - IntegerPart[lat/100]);
long = IntegerPart[long/100] + 100/60 (long/100 - IntegerPart[long/100]);
{lat, long}
]
(* Flush serial port buffer (allow some time to process the previous set of instructions) *)
Pause[5];
DeviceReadBuffer[serial];
(* Loop to query unread SMS messages *)
While[True,
sms = FromCharacterCode@DeviceReadBuffer[serial];
Pause[5];
sms = ImportString[sms, "Table"];
If[sms =!= {},
(* Flush serial port buffer *)
DeviceReadBuffer[serial];
(* Get unread SMS *)
DeviceWrite[serial, "AT+CMGL=\"REC UNREAD\"\r"];
Pause[5];
message=FromCharacterCode@DeviceReadBuffer[serial];
message=ImportString[message,"Table"];
Print[message];
message=Part[message,-3];
Print[message];
(* Check that sms text message matches a pre-defined string *)
If[message === {"position"},
(* Get lat long *)
loc = ToString@Location[];
(* Send location via SMS to destination cell phone in this case the number 111111111 *)
DeviceWrite[serial, "AT+CMGS=\"111111111\"\r"];
DeviceWrite[serial, loc];
DeviceWrite[serial, FromCharacterCode[{26}]];
Pause[10]
];
(* Flush serial port buffer *)
DeviceReadBuffer[serial]
]
]
Let us explain the code step by step. The way the module works is by sending commands over the serial port with DeviceWrite and retrieving the module answer to these commands by reading the contents of the serial port buffer with DeviceReadBuffer. See here and here for more details.
The meaning of the commands in the first couple of lines was already explained in this post The line
DeviceWrite[serial, "AT+CGPSPWR=1\r"];
switchs the embedded GPS on (by default it is turned off). Once the GPS is on it is accessed and controlled with a suite of commands available for that purpose. They are described in the module manual and here we just explain those commands we use. The command
DeviceWrite[serial, "AT+CGPSRST=1\r"];
resets the GPS in "autonomy mode". This is useful if it has been been used recently because it will reduce the time a satellite fix takes. For a first time usage a "cold reset" is the recommended option and the command is
DeviceWrite[serial, "AT+CGPSRST=0\r"];
After doing the reset in either way, the GPS starts tracking satellites. We can query the progress with the command
DeviceWrite[serial, "AT+CGPSINF=0\r"];
Once a fix is achieved this command returns a number of parameters including the position (latitude & longitude) the date and the elevation. To use this information later we have the function Location[] which includes the above command and parses its output returning the latitude and longitude as decimal numbers.
After having sent all the previous commands we need to remove their outputs from the serial port buffer as we do not need them in our program (and we need to have this buffer cleared to process the output of further commands). This is done as follows:
Pause[5];
DeviceReadBuffer[serial];
Once the contents of the buffer are read, it is cleared and we can get the output of the commands which we send next. The reason we used Pause[5] before reading the buffer contents is to make sure that the buffer had enough time to be filled with the output of the previous commands (if we didn't do this the buffer might not be entirely cleared after executing DeviceReadBuffer).
We are now all set to start our main job which is carried out in the While loop. In this loop we read the contents of the serial port buffer every 5 seconds and check the response. When a SMS arrives it generates a so-called "unsolicited response code" which reads +CMTI plus some information. We are not interested in the specific form of the unsolicited code, we only want to detect its presence and this is done with the boolean statement
sms =!= {}
If this returns True then we know that a SMS arrived and then we start its processing. Essentially what we do is reading the SMS and checking that its text agrees with a predefined string which is "position". When that happens then a SMS is sent to a cell phone destination with the latitude and longitude coordinates returned by Location[] (the SMS is sent in the way explained in here. The most important piece of code in this part is:
DeviceWrite[serial, "AT+CMGL=\"REC UNREAD\"\r"];
Pause[5];
message=FromCharacterCode@DeviceReadBuffer[serial];
In the first line we query the module about the unread SMS messages (in this case this is the SMS which just arrived) and we obtain the SMS from the serial port buffer using DeviceReadBuffer as usual. Since we are working with a script we need to make sure that the module had enough time to give the answer to our command and for that we use Pause. The answer of the module is turned into a list of strings which looks like
{{AT+CMGL="REC UNREAD"}, {}, {+CMGL:, 18,"REC UNREAD","sms_sender","","15/05/22,13:58:10+04"}, {position}, {}, {OK}}
The SMS text is at the third position starting from the end of the list.
Running the program
The program can be run by typing in the shell prompt
# wolfram -script GeoLocator.m
where "GeoLocator.m" is the name of the file the Wolfram Language code was saved in. If you let the script run and send a SMS to the geolocator with the text "position" (without quotes) then after some time you should see in the shell terminal the text
{{AT+CMGL="REC UNREAD"}, {}, {+CMGL:, 18,"REC UNREAD","sms_sender","","15/05/22,13:58:10+04"}, {position}, {}, {OK}}
{position}
this confirms that the SMS was received. After a moment you should receive in your destination cell phone the SMS with the geographic coordinates of the geolocator. If you get the SMS text "(0.,0.)" then it means that the GPS did not get a satellite fix yet and it just sent the default location (if the GPS is outdoors it shouldn't take too much time to get a satellite fix).
Finally in a practical geolocation application you might want to run the script as a background process
# wolfram -script GeoLocator.m &
In this way you can log out from your R-Pi and let the script continue running.