Entries in .net (11)

Sunday
27Dec2009

Valve Map Format Utility Library v1.0 Released

Valve Software's Source Engine uses a level editor called Hammer. The files produced by Hammer are in the Valve Map Format (VMF), which are simply text files that define the structure of a map. This file gets passed to a BSP (Binary Space Partitioning) compiler that builds the final product.

A little side project of mine called for a library that could parse VMF files and read the interactive parts of a Source Engine map. So, I built one. The result is the VMF Utility Library. It includes a complete parser, writer, object graph, and special classes for common VMF data types (such as color and vertex). The library is open source and includes documentation, along with a Getting Started tutorial.

I haven't put this one up on CodePlex yet, but you can download it directly (ZIP, 200kb).

Thursday
24Dec2009

Rendering an in-memory Graphviz image with C#

I've been working on a little side-project that involves a lot of graphing. Graphviz has been the most complete solution, but existing options for using it with .NET were rather scarce. WinGraphviz is based on an older version of Graphviz and hasn't been improved since 2004. Plus, it's a COM component, which is yet another thing you have to install on the end-user's machine.

It turns out, however, that the Graphviz library is fairly simple to understand. Once I got the correct DLL configuration copied to my application's directory, the rest of the P/Invoke stuff was easy. On top of it all, Graphviz includes the ability to render an image in-memory, so there's no need to write to the disk and read the result back again. Writing to the disk was the biggest performance bottleneck for my application, so the in-memory rendering feature was a huge relief.

To get started, we need a class to hold our P/Invoke methods and a few helper constants.

public class Graphviz {
    public const string LIB_GVC = "gvc.dll";
    public const string LIB_GRAPH = "graph.dll";
    public const int SUCCESS = 0;
}

To use Graphviz as a library, we first have to create a Graphviz context, which the other methods will use. Add these to our Graphviz class.

/// <summary>
/// Creates a new Graphviz context.
/// </summary>
[DllImport(LIB_GVC)]
public static extern IntPtr gvContext();

/// <summary>
/// Releases a context's resources.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeContext(IntPtr gvc);

Now we need some methods to load and close a graph. These are found in graph.dll.

/// <summary>
/// Reads a graph from a string.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern IntPtr agmemread(string data);

/// <summary>
/// Releases the resources used by a graph.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern void agclose(IntPtr g);

Once we have our graph loaded, we need to apply a layout.

/// <summary>
/// Applies a layout to a graph using the given engine.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvLayout(IntPtr gvc, IntPtr g, string engine);

/// <summary>
/// Releases the resources used by a layout.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeLayout(IntPtr gvc, IntPtr g);

Once the graph has a layout, we can render it.

/// <summary>
/// Renders a graph to a file.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderFilename(IntPtr gvc, IntPtr g,
    string format, string fileName);

/// <summary>
/// Renders a graph in memory.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderData(IntPtr gvc, IntPtr g,
    string format, out IntPtr result, out int length);

Now, you may be thinking that you can just copy gvc.dll and graph.dll over and all will be well. But, sadly, that's not the case. Those DLLs have a few dependencies. You'll need all of the following from your Graphviz installation's /bin directory:

Graphviz loads layout and rendering plugins dynamically using a configuration file, which must also be included alongside your application. If you installed Graphviz using the MSI, there should already be a configuration file in the /bin directory. Mine was called config6, but I'm not sure if the name varies. If you don't have a configuration file, you can generate one with dot -c.

Now that we have the necessary DLLs, we can actually do some rendering!

/// <summary>
/// Renders a DOT string to the specified image format.
/// </summary>
public static byte[] RenderDot(string dot, string format) {
    // Create a Graphviz context
    IntPtr gvc = gvContext();

    // Load the DOT data into a graph
    IntPtr g = agmemread(dot);

    // Apply a layout
    gvLayout(gvc, g, "dot");

    IntPtr result;
    int length;

    // Render the graph
    gvRender(gvc, g, format, out result, out length);

    // Create an array to hold the image
    byte[] image = new byte[length];

    // Copy the image from the IntPtr
    Marshal.Copy(result, image, 0, length);

    // Free up the resources
    gvFreeLayout(gvc, g);
    agclose(g);
    gvFreeContext(gvc);

    return image;
}

Here's the code in action, rendering a graph of the Graphviz DLL dependencies:

string dot = @"digraph Graphviz {
    cdt -> msvcr80, cdt -> kernel32,
    graph -> cdt, graph -> msvcr80, graph -> kernel32,
    gvc -> libexpat, gvc -> ltdl, gvc -> zlib1, gvc -> cdt,
    gvc -> pathplan, gvc -> graph, gvc -> msvcr80, gvc -> kernel32,
    ltdl -> msvcrt, ltdl -> kernel32,
    pathplan -> msvcr80, pathplan -> kernel32,
    zlib1 -> msvcrt, zlib1 -> kernel32,
    libexpat -> kernel32
}";

byte[] image = Graphviz.RenderDot(dot, "png");

And the result:

Wednesday
16Dec2009

Managed NovaCore SDK is up on CodePlex!

There seems to be a lot of interest in a managed version of Novatel Wireless' NovaCore SDK (on the Developer Forums, at least). I've been working on one off and on for a couple of weeks and decided to upload what I have so far to CodePlex.

It's not much, really. So far, the Core module is the most complete of the six and even it is far from done. Everything is basically a 1-to-1 port, so you should just be able to follow along in the NovaCore SDK documentation. The only major change is session management. Instead of releasing sessions manually with a module's ReleaseSession function, the library uses SafeHandles that automatically handle session cleanup during garbage collection. There's also a helper class (NovaCoreSdk) that makes error handling easier.

As the project page says, I'm looking for contributors. To be honest, P/Invoke is definitely not my strong point. The library wouldn't even exist without the good folks over at StackOverflow (especially the P/Invoke master, Jared Par). Preferably, contributors should own a Novatel Wireless device for testing. I have an EVDO device, so it would be great if someone with a HSPA device would come on board to test the HSPA-only features of the SDK.

Monday
07Dec2009

Retrieving modem information from the MiFi with C#

The Novatel Wireless MiFi Mobile Hotspot offers up a surprising amount of diagnostic and status information. My own MiFi.NET library can retrieve most of this information with the MiFi's API. One thing it doesn't offer through the API is the modem's RSSI value, or Received Signal Strength Indication. This value is a negative number expressed in dBm (or decibels per milliwatt) and represents the amount of power in the radio signal. A low RSSI value represents a strong signal (-100 dBm is stronger than -20 dBm, for example).

The API offers signal strength as a value between 1 and 5. If you're looking for the optimal position for your MiFi, however, this just isn't accurate enough. Verizon users can see this value in their web admin portal, but Sprint users don't have this functionality. Fortunately, the page that actually serves up this data is still accessible with a direct URL.

I'm going to show you a simple class that retrieves and parses this information for use in your own applications. Along the way, I'll also explain the different values and what they mean. Note that I will not be including this functionality in MiFi.NET, because it is not part of the official API specification.

To get started, we need a model to represent the modem status information.

public class MiFiModemStatus {
	public string Mdn { get; set; }
	public string Esn { get; set; }
	public string EsnHex { get; set; }
	public int Channel { get; set; }
	public int PRev { get; set; }
	public int PrlId { get; set; }
	public int BandClass { get; set; }
	public int EriVersion { get; set; }
	public bool Dormant { get; set; }
	public int SignalEvdo { get; set; }
	public int Signal1X { get; set; }
	public int BatteryLevel { get; set; }
	public bool BatteryCharging { get; set; }
}

But what do these values mean?

  • MDN - Mobile Directory Number. This is the phone number assigned to your MiFi device.
  • ESN - Electronic Serial Number. This is the unique identifier for your device. All wireless phones are assigned an ESN by the FCC. The MiFi offers this value as both a decimal and in hex format.
  • Channel - The frequency on which the device is operating. Note that this is different from the WiFi channel that you can change in the web admin.
  • P_REV - Interim Standard 95 (also known as IS-95, cmdaOne, or TIA-EIA-95) protocol revision. P_REV 6 is part of the CDMA2000 (3G) standard.
  • PRL ID - Preferred roaming list version. The MiFi maintains an internal list of frequency bands on which it can operate, called the PRL. This number represents the current version of that list.
  • Band Class - The frequency band that the device is operating in.
  • ERI Version - Enhanced roaming indicator version. This value varies between companies, so I can't really tell you exactly what it means.
  • Dormant - If no data is being transferred, the modem will go into a dormant state to save power and this value will be true.
  • EVDO Signal - The current EVDO RSSI value.
  • 1X Signal - The current 1xRTT RSSI value.
  • Battery Level - The battery's current charge level. This value is also available with the API.
  • Battery Charging - True if the battery is currently charging. This value is also available with the API.

To get this information, we need to download and parse the modemstatus.html page. Before doing that, let's write a method to grab the value of each of those fields.

private static string ParseValue(string s, string field) {
	Regex regex = new Regex(string.Format(
		@"<td nowrap>{0}:</td> <td nowrap>(-?\w+)(?: dBm)?</td>", field));
	Match match = regex.Match(s);
	return match.Success ? match.Groups[1].Value : string.Empty;
}

Here, we're using a regular expression to search for the field. We capture the value and also handle the special case for RSSI values (-? allows the negative number to be properly captured, and (?: dBm)? allows for the dBm suffix).

Now we need a method to parse the page as a whole and return a populated model.

private static MiFiModemStatus Parse(string s) {
	return new MiFiModemStatus {
		Mdn = ParseValue(s, "MDN"),
		Esn = ParseValue(s, @"ESN \(decimal\)"),
		EsnHex = ParseValue(s, @"ESN \(hex\)"),
		Channel = int.Parse(ParseValue(s, "Channel")),
		PRev = int.Parse(ParseValue(s, "P_REV Indicator")),
		PrlId = int.Parse(ParseValue(s, "PRL ID")),
		BandClass = int.Parse(ParseValue(s, "Band Class Type")),
		EriVersion = int.Parse(ParseValue(s, "ERI Version")),
		Dormant = ParseValue(s, "Dormancy") == "TRUE",
		SignalEvdo = int.Parse(ParseValue(s, "Signal - EVDO")),
		Signal1X = int.Parse(ParseValue(s, "Signal - 1X")),
		BatteryLevel = int.Parse(ParseValue(s, "Battery Level")),
		BatteryCharging = ParseValue(s, "Battery Charging") == "1"
	};
}

With this method, we simply use the method we wrote earlier to grab each value. Note that we had to escape the parentheses because the field name gets inserted into the regular expression. Without escaping them, (decimal) and (hex) would be read as capture groups.

Finally, we have to actually download the page itself. We'll do this with a static Read method.

public static MiFiModemStatus Read(IPAddress ipAddress) {
	string uri = string.Format("http://{0}/modemstatus.html", ipAddress);
	string text;

	HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
	HttpWebResponse response = (HttpWebResponse)request.GetResponse();
	using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
		text = reader.ReadToEnd();
	}

	return Parse(text);
}

If you're ever done any work with HTTP requests, then this will be familiar. The method just builds a URL from the given IP address and downloads the modem status information. Then, it calls out to our Parse method and returns the populated MiFiModemStatus class.

Easy, huh? Hopefully this will be useful to you, especially in conjunction with MiFi.NET!

Tuesday
24Nov2009

Opf3 Tutorials on CodePlex

I've moved the Object Persistence Framework 3 documentation from the old website to the CodePlex documentation wiki. Hopefully this will make it easier to find examples all in one place.

Please let us know if you find any problems with the materials!