Accessing F1Speed via mobile devices

How I got F1Speed to present an interface on mobile devices, using Nancy.
December 01 2012

This is a big one even if in the end it didn’t take long to implement.  When I originally wrote F1Speed back in march 2012 one of the first requests from my fellow league members was to make a web version so it can be accessed using a mobile phone or tablet.  It wasn’t big on my radar personally because I have four monitors.  Even though it wasn’t big on my radar I wanted to know how to do it and hard it would be.

I started research this last night at about 6pm by typing “self hosted web server” into Google and that led me to the Nancy project.  I’d heard about Nancy on Dot Net Rocks a while back but never done anything with it.

F1Speed is a Windows App using UDP sockets to receive data from Codemasters F1 series.  (F1 2010/2011/2012 etc).  These changes include the use of the following libraries

  • Nancy
  • knockoutjs
  • jquery
  • bootstrap
  • modernizr

Looks like a normal web stack right?  It is.  Nancy replaces ASP.NET, but otherwise we’re all good to go in my normal, comfortable web dev environment.  It took me a little while to get going with Nancy, even though it is very simple. I lost a lot of time trying to put all my resources files, including my views under a subfolder called “Web”.  I wanted my normal F1Speed dll’s and exe and then all the web content the Web folder, just for organisation sake.  Nancy supports custom root paths by implementing the IRootPathProvider interface.  I went down that route and got my views to render but no matter what I tried after searching the web and reading through the help over and over I could not access my static content to (js and css files).  In the end I removed the Web folder and put it all back in route, and let the conventions of Nancy control everything and it all worked.

Getting started with Nancy is as simple as opening nuget and adding the Nancy package and the Nancy self hosting package.  From there you need to configure the Nancy host within the static void Main method, the entry point of your windows app.

class Program
{
    private static NancyHost nancyHost;


    [STAThread]
    static void Main(string[] args)
    {
        Application.ApplicationExit += Application_ApplicationExit;

        var webPort = ConfigurationManager.AppSettings["webport"];
        nancyHost = new NancyHost(new Uri(":" + (string.IsNullOrEmpty(webPort) ? "43920" : webPort)));
        nancyHost.Start();

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1(new TelemetryLapManagerFactory().GetManager()));
    }

    static void Application_ApplicationExit(object sender, EventArgs e)
    {
        nancyHost.Stop();
    }        
}

 

Here we’re just registering the address of the site, which in my case is a single page, the F1Speed dashboard.  I stop Nancy once the application closes.

 

Nancy’s routes are configured using Modules.  A Module is similar to a Controller in MVC, at least it is the way I’ve gone.  The F1Speed web page is pretty simple (as I’ve been saying). I’ve got the single page ‘Dash’ and an API, which publishes JSON data representing the content to display.  Here is my DashModule implementation that derives from NancyModule (as all Modules must).

public class WebSpeedModule : Nancy.NancyModule
{
    public WebSpeedModule()
    {           
        dynamic model = new 
        {   
            Title = "F1 Speed Web Viewer"
        };
        Get["/"] = x => View["dash.sshtml", model];
        
    }
}

Nancy has Get, Put, Delete, Post, (and another I forget), but they’re HTTP verbs… Basically you grab the Method / Verb you want and in it’s indexer you define the route. I only have 1 page.  The most simple version of this delegate is to return a string representing the entire HTML to be rendered.  I’m using the out-of-the-box view engine (SuperSimpleViewEngine).  Which uses razor like syntax, binding to a dynamic object as model.  And that’s what I’ve created above.  I use this model to display the title. That’s it.  All my data comes down from the API via Ajax calls and using knockoutjs.  We’ll get to that.

Here’s my View, my sshtml file.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>@Model.Title</title>
    <link rel="stylesheet" type="text/css" href="/content/css/bootstrap.css" />
    <link rel="stylesheet" type="text/css" href="/content/css/bootstrap-responsive.css" />
    <link rel="stylesheet" type="text/css" href="/content/css/f1speed.css" />

    <script type="text/javascript" src="/content/scripts/modernizr-2.6.2.js"></script>
  </head>
  <body>
    <div class="container-fluid">
        <div class="row-fluid">
          <div class="span3">
            <h4>Speed Delta</h4>
            <div class="numeric-delta" data-bind="text: speedDelta"></div>
          </div>
          <div class="span3">
            <h4>Time Delta</h4>
            <div class="numeric-delta" data-bind="text: timeDelta"></div>
          </div>
        </div>
    </div>
    <script type="text/javascript" src="/content/scripts/jquery-1.8.3.min.js"></script>
    <script type="text/javascript" src="/content/scripts/bootstrap.min.js"></script>
    <script type="text/javascript" src="/content/scripts/knockout-2.2.0.debug.js"></script>
    <script type="text/javascript" src="/content/scripts/dash-vm.js"></script>
    <script>
      $(document).ready(function() {
          ko.applyBindings(DashViewModel)
      });
    </script>
  </body>
</html>

 

You can see @Model.Title displaying the model. If you’re displaying content a user can change then you should use the @!Model.Title syntax instead which will html encode the output of Model.Title.  If you inspect the HTML you noticed I’m making use of the data-bind attribute.  This is knockoutjs at play (you can see the reference at the bottom of the page).  I’ve setup my knockoutjs ViewModel to poll my Web Api.  Here’s my view model.

 

var DashViewModel = function ($, ko) {

    var self = this;
    self.speedDelta = ko.observable();
    self.timeDelta = ko.observable();

    function init() {
        getPacket();
    }
    
    function getPacket() {
        $.ajax({
            url: "/api/packet",
            dataType: "json",
            error: function() {
                self.speedDelta = "ERR";
                self.timeDelta = "ERR";
            },
            success: function(data) {                
                self.speedDelta(data.SpeedDelta);
                self.timeDelta(data.TimeDelta);
                setTimeout(function() { getPacket(); }, 100);
            },
            contentType: "application/json"
        });
    }

    init();

    return {
        speedDelta: self.speedDelta,
        timeDelta: self.timeDelta
    };

}(jQuery, ko)

 

You can see getPacket() does an ajax call to /api/packet which is  a JSON’d version of this class

public class DashViewModel
{
    public string SpeedDelta { get; set; }
    public float TimeDelta { get; set; }        
}

 

Almost as simple as it can get.  But how do we get the data from F1Speed.  As you saw above my reference to Nancy is outside the Windows Form of the normal F1Speed, which hosts the UDP endpoint that receives data.  The central piece is TelemetryLapManager.  This processes the data from Codemasters and keeps track of all data.  Normally the F1Speed windows form polls this manager every 1/20th of a second (update rate is 1/60th of a second, from Codemasters).  When the timer event expires it reads whatever the current data is from the telemetry manager. Ok?  So my API module needs to access this too, but how?  I need some kind of dependency resolution.  Fortunately Nancy comes with it’s own IOC, TinyIoc.  I haven’t read much on it but it appears to automatically register all types as services.  I didn’t know how to configure the container to say TelemetryLapManager is a singleton, and I also didn’t know how to get access to the Tiny Ioc container to inject the manager into windows form.  What I did was create a simple factory that held a static to the telemetry manager and injected that factory into the Form.  You can see it way up top in my first snippet.  Here’s the class. Very basic:

public class TelemetryLapManagerFactory
{
    private static TelemetryLapManager manager;

    public TelemetryLapManager GetManager()
    {
        if (manager == null)
            manager = new TelemetryLapManager();

        return manager;
    }
}

 

Now the API module.  It’s similar to the DashModule, except it takes a constructor dependency on TelemetryLapManagerFactory

public class ApiModule : NancyModule
{
    private readonly TelemetryLapManager _telemetryLapManager;
    
    public ApiModule(TelemetryLapManagerFactory managerFactory) : base("/api")
    {
        _telemetryLapManager = managerFactory.GetManager();

        Get["/packet"] = parameters =>
            {
                var model = new DashViewModel
                    {
                        SpeedDelta = _telemetryLapManager.GetSpeedDelta(),
                        TimeDelta = _telemetryLapManager.GetTimeDelta()
                    };
                
                return Response.AsJson(model);
            };
    }
}

 

All I need to do in the API call is to grab the current info I need from the telemetry lap manager and stuff it into my model and send it across the wire.  You’ll note I’m passing /api into the parent class and calling Get[“/packet”].  You will also note that from the client side I’m calling /api/packet. That’s how the routing engine in Nancy works. 

Tha't’s pretty much it.  The binding in knockoutjs will update the UI whenever it’s view model properties get update.  I’m using setTimeout to call the api and refresh every 100ms.

Total time was about 5 hours and that included losing about 2 hours trying to change the root.

Post a comment

comments powered by Disqus