Accessing Protrack API (An Object-Oriented Approach)

Introduction

 
Accessing Protrack API (https://www.protrack365.com/api.html) is fairly simple, it’s all about creating simple HTTP requests and handling the results which are represented in JSON. The trick here is using OOP concepts to make our code more reusable and more maintainable.
 

Full Code Listing

 

Requirements

 
A Protrack account with one or more GPS devices available for testing. If you are facing permission problems contact your vendor or Protrack technical support.
 

Dependencies

 
Start by adding a reference to Newtonsoft JSON.NET (https://www.newtonsoft.com/json) library to your project. This can be directly downloaded from the vendor’s website or installed through NuGet Manager.
 

Abstraction

 
Base Request
 
Referring to Protrack API we can see that all functions (except the authorization function) accept an access token as a parameter. This token is valid for a certain time (2 hours) and it can be received through authorization only.
 
We will start by creating a base class that will represent the request data. The source code is fairly self-explanatory:
  1. internal abstract class ProtrackRequest {  
  2.   /// <summary>  
  3.   /// Path to function.  
  4.   /// </summary>  
  5.   public abstract string BaseUri { get; }  
  6.   /// <summary>  
  7.   /// This is used by all functions except 'authorization'.  
  8.   /// </summary>  
  9.   public string AccessToken { getset; }  
  10.   
  11.   /// <summary>  
  12.   /// Returns the list of request parameters packaged in a dictionary. Where Key is parameter name and Value is parameter value.  
  13.   /// </summary>  
  14.   public virtual IDictionary<stringobject> GetParams() {  
  15.     var list = new Dictionary<stringobject>();  
  16.     if (AccessToken != null// adding access token only if necessary  
  17.       list.Add("access_token", AccessToken);  
  18.     return list;  
  19.   }  
  20.   
  21.   /// <summary>  
  22.   /// Returns the list of request parameters as a query string.  
  23.   /// </summary>  
  24.   public virtual string GetParamsQueryString() {  
  25.     string queryString = string.Empty;  
  26.   
  27.     foreach (var itm in GetParams()) {  
  28.       // This will keep empty parameters. You can skip them if you like.  
  29.       string valueStr = string.Empty;  
  30.   
  31.       if (itm.Value != null)  
  32.         valueStr = System.Uri.EscapeDataString(itm.Value.ToString());   
  33.   
  34.       queryString += string.Format("{0}={1}&", itm.Key, valueStr);  
  35.     }  
  36.   
  37.     return queryString;  
  38.   }  
  39.   
  40.   /// <summary>  
  41.   /// Returns full request signature (request URI along with parameter query string.)  
  42.   /// </summary>  
  43.   public virtual string GetRequestUri() {  
  44.     return BaseUri + "?" + GetParamsQueryString();  
  45.   }  
  46.   
  47.   public override string ToString() {  
  48.     return GetRequestUri();  
  49.   }  
  50. }  
From the above code we can see that every request class derived from the base will have to fill its path (BaseUri property; mandatory) and parameter list (GetParams() method; optional).
 
Base Response
 
Referring to the Protrack API again we can see that every call response returned from the server, besides being in JSON format, has two common attributes: code and message. The code may refer to one of the error codes available as a list in the API reference, while the message is the description. Keeping those two attributes in mind, we can create our response class,
  1. internal class ProtrackResponse {    
  2.   [JsonProperty("code")]    
  3.   public int Code { get; set; }    
  4.     
  5.   [JsonIgnore]    
  6.   public ProtrackResponseCode ResponseCode { get { return (ProtrackResponseCode)Code; } }    
  7.   [JsonProperty("message")]    
  8.   public string Message { get; set; }    
  9. }      
  10.     
  11. internal enum ProtrackResponseCode {    
  12.   Success = 0,    
  13.   SystemError = 10000,    
  14.   UnknownRequest = 10001,    
  15.   LoginTimeout = 10002,    
  16.   Unauthorized = 10003,    
  17.   ParameterError = 10004,    
  18.   MissingParameter = 10005,    
  19.   ParamOutOfRange = 10006,    
  20.   PermissionDenied = 10007,    
  21.   RequestLimit = 10009,    
  22.   AccessTokenNotExist = 10010,    
  23.   AccessTokenInvalid = 10011,    
  24.   AccessTokenExpired = 10012,    
  25.   ImeiUnauthorized = 10013,    
  26.   RequestTimeError = 10014,    
  27.   LoginFailed = 20001,    
  28.   TargetNotExist = 20005,    
  29.   DeviceOffline = 20017,    
  30.   SendCommandFailed = 20018,    
  31.   NoData = 20023,    
  32.   TargetExpired = 20046,    
  33.   Unsupported = 20048    
  34. }     
As response class needs to be instantiated, we cannot just mark it as abstract. Abstract classes cannot be instantiated.
 

API Wrapper

 
Now for the actual code that connects things together. This code represents the wrapper itself. The code is very generic. We will add function wrappers later.
  1. class ProtrackWrapper {  
  2.   protected string Account { getset; }  
  3.   protected string Password { getset; }  
  4.   /// <summary>  
  5.   /// API base URI  
  6.   /// </summary>  
  7.   protected string BaseUri { get { return "http://api.protrack365.com"; } }  
  8.   /// <summary>  
  9.   /// This will be used for all requests  
  10.   /// </summary>  
  11.   public string AccessToken { getprotected set; }  
  12.   /// <summary>  
  13.   /// Access token expiry date  
  14.   /// </summary>  
  15.   public DateTime? AccessTokenExpiresOnUtc { getprotected set; }  
  16.   
  17.   
  18.   public ProtrackWrapper(string account, string password) {  
  19.     this.Account = account;  
  20.     this.Password = password;  
  21.   }  
  22.   
  23.   /// <summary>  
  24.   /// Returns a response from a web resource.  
  25.   /// </summary>  
  26.   /// <returns>Response represented as string.</returns>  
  27.   protected static string GetResponse(string requestUri) {  
  28.     HttpWebRequest req = WebRequest.CreateHttp(requestUri);  
  29.     using (var rsp = req.GetResponse())  
  30.     using (var stm = rsp.GetResponseStream())  
  31.     using (var rdr = new StreamReader(stm)) {  
  32.       return rdr.ReadToEnd();  
  33.     }  
  34.   }  
  35.   
  36.   public virtual T GetResponse<T>(ProtrackRequest req) where T : ProtrackResponse {  
  37.     var requestUrl = new Uri(new Uri(BaseUri), req.GetRequestUri()).ToString();  
  38.   
  39.     var rspStr = GetResponse(requestUrl);  
  40.   
  41.     T rsp = JsonConvert.DeserializeObject<T>(rspStr);  
  42.     // should not throw a generic exception,   
  43.     // and should not throw an exception for just everything  
  44.     // we will just keep this for now!  
  45.     if (rsp.Code != 0)  
  46.       throw new Exception(rsp.Message);  
  47.   
  48.     return rsp;  
  49.   }  
  50. }  
As you can see in the above code, we used static polymorphism to create two versions of GetResponse(), one that returns a bare response for the bare request URI, and another one that accepts a typed request object and returns a typed response object. In fact, the other version is a generic one that returns only objects that derive from ProtrackResponse.
 

Authorization

 
To call any API function you need an access token, and this can be retrieved through the authorization function. The authorization function accepts three arguments: request time (in Unix format), the username (i.e. account), and signature.
 

Unix Time

 
As described by Wikipedia, Unix time (also known as POSIX time or UNIX Epoch time) is a system for describing a point in time. It is the number of seconds that have elapsed since 00:00:00 Thursday, 1 January 1970, Coordinated Universal Time (UTC), minus leap seconds.
Unix time format will be used throughout the API, so we have created a helper class for it,
  1. static class UnixTimeHelper {  
  2.   /// <summary>  
  3.   /// Converts DateTime to Unix time.  
  4.   /// </summary>  
  5.   public static long ToUnixTime(this DateTime time) {  
  6.     var totalSeconds = (long)(time.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;  
  7.   
  8.     if (totalSeconds < 0)  
  9.       throw new ArgumentOutOfRangeException("Unix time starts Jan 1 1970 00:00:00");  
  10.   
  11.     return totalSeconds;  
  12.   }  
  13.   /// <summary>  
  14.   /// Converts Unix time to DateTime.  
  15.   /// </summary>  
  16.   public static DateTime ToDateTime(long unixTime) {  
  17.     return new DateTime(1970, 1, 1).Add(TimeSpan.FromSeconds(unixTime));  
  18.   }  
  19. }  
You do not have to worry about leap seconds as System.DateTime does not take leap seconds into account.
 

Signature

 
Signature is an MD5 hash of a combination of MD5 password hash and request time (in Unix format). In other words:
signature = MD5 ( MD5(password) + unix_time )
 
Signature is represented as 32 bytes lower-case string.
 

Authorization Request

 
The authorization request class is as follows,
  1. internal class ProtrackAuthorizationRequest : ProtrackRequest {  
  2.   /// <summary>  
  3.   /// Path to function.  
  4.   /// </summary>  
  5.   public override string BaseUri { get { return "api/authorization"; } }  
  6.   public string Account { getprotected set; }  
  7.   protected string Password { getset; }  
  8.   public DateTime RequestTimeUtc { getprivate set; }  
  9.   
  10.   public ProtrackAuthorizationRequest() { }  
  11.   public ProtrackAuthorizationRequest(string account, string password) {  
  12.     this.Account = account;  
  13.     this.Password = password;  
  14.   }  
  15.   
  16.   public override IDictionary<stringobject> GetParams() {  
  17.     RequestTimeUtc = DateTime.UtcNow;  
  18.     var unixTime = UnixTimeHelper.ToUnixTime(RequestTimeUtc);  
  19.   
  20.     string signature = GetSignature(unixTime);  
  21.   
  22.     var list = base.GetParams(); // retrieving base parameters (if any)  
  23.     list.Add("time", unixTime);  
  24.     list.Add("account"this.Account);  
  25.     list.Add("signature", signature);  
  26.   
  27.     return list;  
  28.   }  
  29.   
  30.   private string GetSignature(long unixTime) {  
  31.     // signature is md5(md5(password) + time) encoded as a 32 bytes lower-case characters.  
  32.     var signature = ProtrackHelper.HashMD5(this.Password);  
  33.     signature = ProtrackHelper.HashMD5(signature + unixTime.ToString());  
  34.     return signature;  
  35.   }  
  36. }  
As you can see, you need to provide the function path through BaseUri. And by overriding GetParams() you can provide your parameter list.
 
To make things work, here’s the declaration of the MD5 hashing function:
  1. static class ProtrackHelper {  
  2.   public static string HashMD5(string input) {  
  3.     byte[] data = System.Text.Encoding.UTF8.GetBytes(input);  
  4.     data = System.Security.Cryptography.MD5.Create().ComputeHash(data);  
  5.     return BitConverter.ToString(data).Replace("-""").ToLower();  
  6.   }  
  7. }  

Authorization Response

 
The authorization response class is fairly simple. It reflects the JSON response data returned from the server. While ProtrackAuthorizationResponse focuses on authorization attributes, the base ProtrackResponse has the two common attributes, code and message.
  1. internal class ProtrackAuthorizationResponse : ProtrackResponse {  
  2.   [JsonProperty("record")]  
  3.   public ProtrackAuthorizationRecord Record { getset; }  
  4. }  
  5.   
  6. internal class ProtrackAuthorizationRecord {  
  7.   [JsonProperty("access_token")]  
  8.   public string AccessToken { getset; }  
  9.   [JsonProperty("expires_in")]  
  10.   public int ExpiresInSeconds { getset; }  
  11. }  
We tagged properties with JsonPropertyAttribute attribute to allow our code to use different names for properties.
 

Connecting Things Together

 
Now we can add the following authorization code to the wrapper class,
  1. public void Authorize() {  
  2.   this.AccessToken = null;  
  3.   this.AccessTokenExpiresOnUtc = null;  
  4.   
  5.   var req = new ProtrackAuthorizationRequest(this.Account, this.Password);  
  6.   var rsp = GetResponse<ProtrackAuthorizationResponse>(req);  
  7.     
  8.   // updating access token and expiration time  
  9.   this.AccessToken = rsp.Record.AccessToken;  
  10.   this.AccessTokenExpiresOnUtc = req.RequestTimeUtc.AddSeconds(rsp.Record.ExpiresInSeconds);  
  11. }  
Now test your code and check if everything is going well,
  1. var wrapper = new ProtrackWrapper("test""123456");  
  2. wrapper.Authorize( );  
  3. Console.WriteLine("Authorization code is: {0}", wrapper.AccessToken);.  
  4. // Prints:  
  5. // Authorization code is: A156321......69a0ef614ef3f582  

Tracking

 
Now that everything is going well, we can move next to the tracking function. The tracking function accepts one or more GPS device server IMEI codes and returns the latest coordinates for each code. Device server IMEI can be found through the Protrack web/mobile interface or through Param# command (specific GPS device models only.)
 
Track Request
 
Now that we have our requirement list, create the request class:
  1. internal class ProtrackTrackRequest : ProtrackRequest {  
  2.   public override string BaseUri { get { return "api/track"; } }  
  3.   public string[] ImeiList { getset; }  
  4.   
  5.   public ProtrackTrackRequest() {  
  6.   
  7.   }  
  8.   
  9.   public ProtrackTrackRequest(string accessToken, string[] imeiList) {  
  10.     this.AccessToken = accessToken;  
  11.     this.ImeiList = imeiList;  
  12.   }  
  13.   
  14.   public override IDictionary<stringobject> GetParams() {  
  15.     var list = base.GetParams();  
  16.     list.Add("imeis"string.Join(",", ImeiList));  
  17.   
  18.     return list;  
  19.   }  
  20. }  

Track Response

The response class lists the attributes that are returned from the server. I did not list all attributes, just for clarity.
  1. internal class ProtrackTrackResponse : ProtrackResponse {  
  2.   [JsonProperty("record")]  
  3.   public ProtrackTrackRecord[] Records { getset; }  
  4. }  
  5.   
  6. internal class ProtrackTrackRecord {  
  7.   [JsonProperty("imei")]  
  8.   public string IMEI { getset; }  
  9.   [JsonProperty("longitude")]  
  10.   public decimal Longitude { getset; }  
  11.   [JsonProperty("latitude")]  
  12.   public decimal Latitude { getset; }  
  13.   [JsonProperty("systemtime")]  
  14.   public long SystemUnixTime { getset; }  
  15.   [JsonProperty("gpstime")]  
  16.   public long GpsUnixTime { getset; }  
  17.   
  18.   // To make things easier, we have made extra DateTime properties  
  19.   // An alternative is to create a custom JSON converter for unix time  
  20.   public DateTime SystemTimeUtc { get { return UnixTimeHelper.ToDateTime(SystemUnixTime); } }  
  21.   public DateTime GpsTimeUtc { get { return UnixTimeHelper.ToDateTime(GpsUnixTime); } }  
  22.   
  23.   // add any field you like  
  24. }   
Connecting Things Together
 
Now add the following code to the wrapper class. Notice how we test access token expiration before making our request:
  1. public ProtrackTrackRecord Track(string imei) {  
  2.   return Track(new string[] { imei })[0];  
  3. }  
  4. public ProtrackTrackRecord[] Track(string[] imeiList) {  
  5.   if (this.AccessToken == null || DateTime.UtcNow >= this.AccessTokenExpiresOnUtc) {  
  6.     Authorize();  
  7.   }  
  8.   
  9.   var req = new ProtrackTrackRequest(this.AccessToken, imeiList);  
  10.   var rsp = GetResponse<ProtrackTrackResponse>(req);  
  11.   
  12.   return rsp.Records;  
  13. }  
And test:
  1. var track = wrapper.Track("123456789012345");  
  2. Console.WriteLine("{0},{1},{2}", track.Latitude, track.Longitude, track.GpsTimeUtc);  
  3. // Prints  
  4. // 30.193456, 31.463092, 15 / 07 / 2019 19:41:38  
One Step Further
 
In the previous response code as you can notice in those lines we have added two extra properties to convert Unix time to DateTime,
  1. public DateTime SystemTimeUtc { get { return UnixTimeHelper.ToDateTime(SystemUnixTime); } }  
  2. public DateTime GpsTimeUtc { get { return UnixTimeHelper.ToDateTime(GpsUnixTime); } }  
An alternative is to use a JSON converter,
  1. internal class JsonUnixTimeConverter : Newtonsoft.Json.Converters.DateTimeConverterBase {  
  2.   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {  
  3.     if (reader.TokenType != JsonToken.Integer)  
  4.       throw new Exception("Unexpected token type.");  
  5.   
  6.     var unixTime = (long)reader.Value;  
  7.   
  8.     return UnixTimeHelper.ToDateTime(unixTime);  
  9.   }  
  10.   
  11.   public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {  
  12.     if (false == value is DateTime)  
  13.       throw new Exception("Unexpected object type.");  
  14.   
  15.     var dateTime = (DateTime)value;  
  16.   
  17.     var unixTime = UnixTimeHelper.ToUnixTime(dateTime);  
  18.   
  19.     writer.WriteValue(unixTime);  
  20.   }  
  21. }  
You will need to modify the ProtrackTrackRecord class,
  1. [JsonProperty("systemtime")]  
  2. public DateTime SystemTimeUtc { getset; }  
  3. [JsonProperty("gpstime")]  
  4. public DateTime GpsTimeUtc { getset; }  
And ProtrackWrapper.GetResponse<T> method,
  1. T rsp = JsonConvert.DeserializeObject<T>(rspStr, new JsonUnixTimeConverter());  

Playback

 
Besides the access token, the playback method accepts single IMEI code, range start time and end time (both in Unix format.)
 
Playback Request
 
You can easily guess the request class code,
  1. internal class ProtrackPlaybackRequest : ProtrackRequest {  
  2.   public override string BaseUri { get { return "api/playback"; } }  
  3.   public string Imei { getset; }  
  4.   public DateTime BeginTimeUtc{ getset; }  
  5.   public DateTime EndTimeUtc { getset; }  
  6.   
  7.   public ProtrackPlaybackRequest() {  
  8.   
  9.   }  
  10.   
  11.   public ProtrackPlaybackRequest(string accessToken, string imei, DateTime beginTimeUtc, DateTime endTimeUtc) {  
  12.     this.AccessToken = accessToken;  
  13.     this.Imei = imei;  
  14.     this.BeginTimeUtc = beginTimeUtc;  
  15.     this.EndTimeUtc = endTimeUtc;  
  16.   }  
  17.   
  18.   public override IDictionary<stringobject> GetParams() {  
  19.     var list = base.GetParams();  
  20.     list.Add("imei"this.Imei);  
  21.     list.Add("begintime", UnixTimeHelper.ToUnixTime(BeginTimeUtc));  
  22.     list.Add("endtime", UnixTimeHelper.ToUnixTime(EndTimeUtc));  
  23.   
  24.     return list;  
  25.   }  
  26. }  
Playback Response
 
The response class is fairly simple too,
  1. internal class ProtrackPlaybackResponse : ProtrackResponse {  
  2.   [JsonProperty("record")]  
  3.   public string RecordString { getset; }  
  4.   
  5.   // a custom JSON converter can be used here too  
  6.   public ProtrackPlaybackRecord[] GetRecords() {  
  7.     var recordsStrList = RecordString.Split(';');  
  8.     List<ProtrackPlaybackRecord> records = new List<ConsoleApp.ProtrackPlaybackRecord>(recordsStrList.Length);  
  9.   
  10.     foreach (var recordStr in recordsStrList) {  
  11.       if (recordStr.Length == 0)  
  12.         continue;  
  13.   
  14.       var record = new ProtrackPlaybackRecord(recordStr);  
  15.       records.Add(record);  
  16.     }  
  17.   
  18.     return records.ToArray();  
  19.   }  
  20. }  
  21.   
  22. internal class ProtrackPlaybackRecord {  
  23.   public decimal Longitude { getset; }  
  24.   public decimal Latitude { getset; }  
  25.   public DateTime GpsTimeUtc { getset; }  
  26.   public int Speed { getset; }  
  27.   public int Course { getset; }  
  28.   
  29.   public ProtrackPlaybackRecord() {  
  30.   
  31.   }  
  32.   public ProtrackPlaybackRecord(string str) {  
  33.     string[] args = str.Split(',');  
  34.   
  35.     Longitude = decimal.Parse(args[0]);  
  36.     Latitude = decimal.Parse(args[1]);  
  37.     GpsTimeUtc = UnixTimeHelper.ToDateTime(int.Parse(args[2]));  
  38.     Speed = int.Parse(args[3]);  
  39.     Course = int.Parse(args[4]);  
  40.   }  
  41. }  

Connecting Things Together

 
ProtrackWrapper code,
  1. public ProtrackPlaybackRecord[] Playback(string imei, DateTime beginTimeUtc, DateTime endTimeUtc) {  
  2.   if (this.AccessToken == null || DateTime.UtcNow >= this.AccessTokenExpiresOnUtc) {  
  3.     Authorize();  
  4.   }  
  5.   
  6.   var req = new ProtrackPlaybackRequest(this.AccessToken, imei, beginTimeUtc, endTimeUtc);  
  7.   var rsp = GetResponse<ProtrackPlaybackResponse>(req);  
  8.   
  9.   return rsp.GetRecords();  
  10. }  
And test,
  1. var records = wrapper.Playback("123456789012345", DateTime.UtcNow, DateTime.Today);  
  2. foreach (var rec in records)  
  3.   Console.WriteLine("{0},{1},{2}", rec.GpsTimeUtc, rec.Latitude, rec.Longitude);  

What’s Next

 
Using the above-mentioned mechanism, you can easily create wrappers for the rest of the API functions. I will be happy to receive your feedback and comments over this code.


Similar Articles