HTTP Headers threading

Running the HTTP requests and the NMEA stack on the foreground thread was always going to be a bit marginal. There were regular buffer overflows and memory allocation issues which the code would not always recover from. The simplest approach was to modify the code so a new thread is spawned for each HTTP request.

static void Gps_PositionChangedV5(uint data1, uint data2, DateTime time)
{
...
Thread thread = new Thread(() => HandleRequestSyncV5(Gps.Fix3D, Gps.GPSTime, Gps.Latitude, Gps.Longitude, Gps.HDoP, Gps.Altitude, Gps.Kmh, Gps.TrackAngle));
Thread.Start();
}

static void HandleRequestSyncV5(bool fix3D, DateTime gpsTime, float latitude, float longitude, float hDop, float altitude, float kmh, float trackAngle)
{
try
{
Stopwatch stopwatch = Stopwatch.StartNew();

using (HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(CloudServerUrl + "posV4.aspx"))
{
stopwatch.Stop();
...

10 requests to gpstrackerhttpheaders.cloudapp.net
1172,1143,1182,1136,1178,1257,1110,1117,1099,1225
Average 1161 mSec

10 requests to IP Address
1031,834,1033,1086,1029,1108,1049,1072,1036,819
Average 1010 mSec

The application is now quite stable but at roughly 57K2 bytes there isn’t a lot of space for any business functionality…

HTTP Headers Request reduction

Approaching the minimal request & response payload sizes.

Request Bytes Sent: 192
POST http://gpstrackerhttpheaders.cloudapp.net/posV3.aspx HTTP/1.1
x-Pos: 5C-86-4A-00-3F-63 20130126054537 T -43.00000 172.00000 12.2 4.08 1 83
Content-Length: 0
Connection: Keep-Alive
Host: gpstrackerhttpheaders.cloudapp.net

Response Bytes Received:113
HTTP/1.1 200 OK
Cache-Control: private
x-UpdMin: 30
Date: Sat, 26 Jan 2013 05:45:37 GMT
Content-Length: 0

10 requests to gpstrackerhttpheaders.cloudapp.net
944,969,1246,1043,1282,948,940,968,980,968
Average 1029 mSec

10 requests to IP Address
692,696,691,702,693,684,691,692,689,693
Average 692 mSec

Removing anymore headers from the request or response could break could HTTP break compatibility.

I went back and timed a V1 request & response to see what impact the reduction in payload size was…

10 requests to gpstrackerhttpheaders.cloudapp.net
2534,1457,1426,1571,1453,1462,1516,1422,1416,1421
Average 1569 mSec

10 requests to IP Address
1222,1200,1221,1238,1210,1266,1199,1235,1218,1230,1215
Average 1345 mSec

V1 573 bytes – 1569mSec & 1345 mSec
V2 480 bytes
V3 376 bytes
V4 305 bytes – 1029 mSec & 692 mSec

Most probably another 20 or 30 bytes could be saved by removing the ‘-‘ from the MAC address, shorter url, fixed width fields with no delimiter, removing the keep alive etc.

During my testing the NMEA stack was quite unstable it would fail with buffer overflows and overruns. The HTTP client requests are blocking and sharing the main execution thread meant the NMEA stack was unable to keep up.

HTTP Headers request duration Netduino+

After the byte counting I figured it was time to check the duration of the requests. I used a stopwatch class which was an approximate equivalent of the full .Net BCL one from netduino.com.

My house is cabled with CAT5 and I have a patch panel in a hall cupboard with gigabit switch and ADSL modem (Fibre is coming to my street soon). The Netduino was plugged into one of the spare ports and was running MF V4.2.

I then timed 10 requests to gpstrackerhttpheaders.cloudapp.net
1780, 1512, 1621, 1516, 1617, 1544, 1672, 1618,1490, 1614
Average 1599 mSec

My initial thought was maybe name resolution was broken so I replaced the hostname in the MF client with its IP address

1099, 1006, 1182, 1006, 1008, 1104,1030, 1092,1002, 1170
Average 1070 mSec – faster but not great

I then built a quick ‘n’ dirty desktop console application for comparison purposes.

gpstrackerhttpheaders.cloudapp.net
965, 243, 246, 242, 242, 249, 240, 242, 241, 518
Average 343 mSec

IP Address
603, 247, 241, 240, 241, 240, 239, 253, 243, 251
Average 280 mSec

The desktop times tally with what I see with other client applications calling Azure instances in the Singapore, West US and South Central US data centres.

I then did some digging and found others with similar results. These durations would go some way to explaining why the NMEA stack was having problems with buffer over runs and data corruption.

Time to move the HTTP request code off to a separate thread. Then have a look at system.http with Redgate Reflector  and see if anything looks suspicious.

HTTP Headers response reduction

In the previous post the response was still looking a bit chunky

Response – Bytes Received: 217
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/7.0
x-UpdMin: 30
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 19 Jan 2013 10:25:12 GMT
Content-Length: 0

The HTTP headers returned by IIS were a significant portion of the response. I found that there were several different approaches but the simplest to setup was discussed here.

Response – Bytes Received: 113
HTTP/1.1 200 OK
Cache-Control: private
x-UpdMin: 30
Date: Sun, 20 Jan 2013 01:51:37 GMT
Content-Length: 0

V1 573 bytes
V2 480 bytes
V3 376 bytes

With a little effort roughly 200 bytes or 1/3 reduction in payload size. Still some scope for further reductions…

HTTP Headers request reduction

The HTTP protocol is pretty light weight but in some cases the overhead can impact on performance\operational costs\scalability etc. So I fired up Fiddler to have a look at what was going on.

Request – Bytes Sent: 305
POST http://gpstrackerhttpheaders.cloudapp.net/posV1.aspx HTTP/1.1
x-DeviceMacAddress: 5C-86-4A-00-3F-63
x-3DFix: True
x-GPSTime: 2011 06 01 01:52:05
x-Latitude: -43.XXXXX
x-Longitude: 172.XXXXX
x-HDoP: 0.83
x-Altitude: 24.1
x-Speed: 0
x-Heading: 0
Content-Length: 0
Connection: Keep-Alive
Host: gpstrackerhttpheaders.cloudapp.net

Response – Bytes Received: 278
HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/7.0
Set-Cookie: ASP.NET_SessionId=2giugnxvke4iv3vtekln1n0k; path=/; HttpOnly
x-UpdateIntervalSecs: 30
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 19 Jan 2013 10:00:49 GMT
Content-Length: 0

The first step was to shorten the header names (this could be taken to extremes with short names and a limited number of headers) which would reduce the size of the request and remove the ASP.Net session state information from the response.

Request – Bytes Sent: 263
POST http://gpstrackerhttpheaders.cloudapp.net/posV2.aspx HTTP/1.1
x-ID: 5C-86-4A-00-3F-63
x-3D: True
x-Time: 2011 06 01 02:16:26
x-Lat: -43.XXXXX
x-Lon: 172.XXXXX
x-HDoP: 0.92
x-Alt: 25.0
x-Spd: 1
x-Hdg: 0
Content-Length: 0
Connection: Keep-Alive
Host: gpstrackerhttpheaders.cloudapp.net

Response – Bytes Received: 217
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/7.0
x-UpdMin: 30
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 19 Jan 2013 10:25:12 GMT
Content-Length: 0

With minimal effort the payloads were reduced by roughly 90 bytes (V1 573 bytes V2 480 bytes),

HTTP Headers baseline

Using the HttpWebRequest functionality in system.http and using the HTTP headers to upload the GPS position data is a pretty simple and low code approach. For this initial version I’m uploading the

  • Device Mac Address
  • 2D vs 3D fix
  • Latitude
  • Longitude
  • Horizontal dilution of position (HDoP)
  • Altitude
  • Speed
  • Heading

The server responds with

  • Minimum time between position reports

This approach does have some disadvantages

Adding system.http increases the size of the download to by roughly 40K which on the Netduino Plus could be a problem. The HTTP requests and responses can also be a bit chunky. The HttpWebRequest.GetResponse call is synchronous which could cause some issues with the processing of the GPS NMEA data stream (particularly when there are connectivity problems).

I’ll be looking at solutions to these issues in future posts

Client application

using (HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(CloudServerUrl))
{
request.Method = "POST";
request.Headers.Add("x-DeviceMacAddress", DeviceMacAddress());
request.Headers.Add("x-3DFix", Gps.Fix3D.ToString());
request.Headers.Add("x-GPSTime", DateTime.Now.ToString("yyyy MM dd hh:mm:ss"));
request.Headers.Add("x-Latitude", Gps.Latitude.ToString("F5"));
request.Headers.Add("x-Longitude", Gps.Longitude.ToString("F5"));
request.Headers.Add("x-HDoP", Gps.HDoP.ToString("F2"));
request.Headers.Add("x-Altitude", Gps.Altitude.ToString("F1"));
request.Headers.Add("x-Speed", Gps.Kmh.ToString("F0"));
request.Headers.Add("x-Heading", Gps.TrackAngle.ToString("F0"));
request.ContentLength = 0;


using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Debug.Print(" HTTP Status:" + response.StatusDescription);

if (response.StatusCode == HttpStatusCode.OK)
{
if (response.Headers["x-UpdateIntervalSecs"] != null)
{
positionUpdateIntervalMinimum = new TimeSpan(0, 0, int.Parse(response.Headers["x-UpdateIntervalSecs"].ToString()));
}
}
}

On the server side

protected void Page_Load(object sender, EventArgs e)
{
if (this.Request.Headers["x-DeviceMacAddress"] == null)
{
return;
}
string deviceMacAddress = this.Request.Headers["x-DeviceMacAddress"];


if (this.Request.Headers["x-GPSTime"] == null)
{
return;
}
DateTime gpsTime = DateTime.ParseExact(this.Request.Headers["x-GPSTime"], "yyyy MM dd hh:mm:ss", CultureInfo.InvariantCulture);

if (this.Request.Headers["x-3DFix"] == null)
{
return;
}
bool is3DFix = bool.Parse(this.Request.Headers["x-3DFix"]);

if (this.Request.Headers["x-Latitude"] == null)
{
return;
}
Double latitude = Double.Parse(this.Request.Headers["x-Latitude"]);

if (this.Request.Headers["x-Longitude"] == null)
{
return;
}
Double longitude = Double.Parse(this.Request.Headers["x-Longitude"]);

if (this.Request.Headers["x-HDoP"] == null)
{
return;
}
Double hDoP = Double.Parse(this.Request.Headers["x-HDoP"]);

if (this.Request.Headers["x-Altitude"] == null)
{
return;
}
Double altitude = Double.Parse(this.Request.Headers["x-Altitude"]);

if (this.Request.Headers["x-Speed"] == null)
{
return;
}
int speed = int.Parse(this.Request.Headers["x-Speed"]);

if (this.Request.Headers["x-Heading"] == null)
{
return;
}
int heading = int.Parse(this.Request.Headers["x-Heading"]);

this.Response.Headers.Add("x-UpdateIntervalSecs", "30") ;
}

Source for HTTP Headers Client V1, and HTTP Headers ServiceV1

GPS Netduino Plus demo platform

For the initial posts the platform will be a Netduino Plus based and connected to the internet via cable. Once the application is robust enough I’ll take make it portable using a GSM Modem.

The BoM for the Netduino client posts is

Netduino Plus and GPS

NetMF HTTP Debugging with Fiddler

Debugging problems with HTTP based connectivity on NetMF clients can be a bit awkward, particularly if the server side application is Azure based. You can debug an Azure application locally and call if from a NetMF device attached to you local network with a couple of hacks….

First configure fiddler to accept connections from remote clients and note down the port number

Fiddler Options

Connections Tab

On the device change the request URL to be a machine local one (cut n paste from the browser) for debugging using the Azure emulator or cloud one

private const string CloudServerUrl = @"http://127.0.0.1:81/positionUpdate.aspx";
or
private const string CloudServerUrl = @"http://myapplication.cloudapp.net/PositionUpdate.aspx";

Then setup the NetMF HTTP request proxy endpoint to be Fiddler

request.Headers.Add("x-DeviceMacAddress", DeviceMacAddress());
request.Headers.Add("x-3DFix", Gps.Fix3D.ToString());
request.Headers.Add("x-GPSTime", Gps.GPSTime.ToString("yyyy MM dd hh:mm:ss"));
request.Headers.Add("x-Latitude", Gps.Latitude.ToString("F6"));
request.Headers.Add("x-Longitude", Gps.Longitude.ToString("F6"));
request.Headers.Add("x-HDoP", Gps.hDop.ToString("F2"));
request.Headers.Add("x-Altitude", Gps.Altitude.ToString("F1"));
request.ContentLength = 0;

request.Proxy = new WebProxy("10.0.0.56", 8888 );

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Debug.Print(" HTTP Status:" + response.StatusDescription);
if (response.StatusCode != HttpStatusCode.OK)
{
....
}
}

Then you should be able to see the requests & responses in Fiddler

POST http://127.0.0.1:81/positionUpdate.aspx HTTP/1.1
x-DeviceMacAddress: 5C-86-4A-00-3E-6B
x-3DFix: True
x-GPSTime: 2013 01 09 08:31:32
x-Latitude: -43.XXXX
x-Longitude: 172.XXXX
x-HDoP: 1.21
x-Altitude: 1.6
Content-Length: 0
Connection: Keep-Alive
Host: 127.0.0.1:81

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 09 Jan 2013 08:31:32 GMT
Content-Length: 17

Custom MMA8451Q SeeedStudio Twig

Lastnight wired up a SeeedStudio Grove system compatible twig based on one of the MMA8451Q breakout boards I bought. It has two connectors one for the I2C interface the other for the two interrupt lines which the device has.

MMA8451Q SeeedStudio Twig

I can read acceleration values from the device and once I have the 14bit 2s complement value conversion thouroughly tested I will add code to to configure the interrupt functionality.

Need to make connector leads a bit longer on V2…

Netduino and MMA8451Q I2C

After an hour mucking around trying to figure out why my new MMA8451Q accelerometer breakout board was returning odd values I stumbled across this post which lead to this post. Then I went back to the device data sheet and found the key paragraph I had missed

A LOW to HIGH transition on the SDA line while the SCL line is high is defined as a stop condition (STOP). A data transfer is always terminated by a STOP. A Master may also issue a repeated START during a data transfer. The MMA8451Q expects repeated STARTs to be used to randomly read from specific registers.

After implementing the approach suggested in this post I can now reliably read registers on the device.

This appears to be applicable for the MMA8451Q, MMA8452Q and MMA8453Q devices.