CAB GPS Sample
In last week's web cast, Ed and I showed a very simple example of CAB. Actually, this is based on the post I wrote some days ago and on the Community Drop bits.
Basically the structure of the app is the following:
There are two components: GPSListener and GPSListenerMock, both implement a common interface IGPSListener. We use the first one to actually read the GPS data stream from the serial port. The Mock is just that, a mock for testing or for demo purposes and does not require the actual device to be connected to the machine.
The interface is very simple:
public interface IGPSListener
{
event EventHandler<GPSQuickStart.GPSEventArgs> NewPosition;
void Start();
void Stop();
}
The actual listener spawns a thread in the Start method and continually reads from the serial port for the data stream:
public void Start()
{
//TODO: Hardcoded port settings, should use config
port = new SerialPort("COM2", 4800, Parity.None, 8, StopBits.One );
port.Open();isRunning = true;
worker = new Thread(new ThreadStart(Run));
worker.Start();
}
The worker simple enters a loop where each "packet" received from the serial port (a line really) gets parsed and a new event is raised:
private void Run()
{
try
{
while (isRunning)
{
string line = port.ReadLine();
GPSData gpsdata = ParseData(line);
if( gpsdata != null )
{
NewPosition( this, new GPSEventArgs( gpsdata ) ); }Thread.Sleep(500);
}
}
catch( Exception ex )
{
//TODO: Ugly exception handling
System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
throw ex;
}
}
The parse helper function simply discards all packets we are just not interested in (note that we are skiping much of the error handling or consistency of the packets, I'm ignoring them for simplicity):
private GPSData ParseData(string line)
{
//TODO: very simple parser
//No error control, no verification whatsoever
if (line.StartsWith("$GPGLL") == false)
{
return null;
}string[] data = line.Split(',');
GPSData d = new GPSData();
d.Latitude = double.Parse(data[1]) / 100;
d.Longitude = double.Parse(data[3]) / 100;
d.rawString = line;
return d;
}
The key of course is in line:
NewPosition( this, new GPSEventArgs( gpsdata ) );
This is the event declared in the body of the class and specified in the interface, also decorated with CAB attributes that instructs the CAB infrastructure to wire it up to the EventBroker:
[EventPublication("event://GPSQuickStart/NewPosition")]
public event EventHandler<GPSEventArgs> NewPosition;
The mock class simply simulated data and sends events with fake data.
On the other side sit the controller and main form of the application. The GPSController is just a simple class that derives Controller that encapsulates the behaviour of the form. In this case, it just subscribes to the events we are interested in (only one: NewPosition) and updates the shared state:
public class GPSController : Controller
{
[EventSubscription("event://GPSQuickStart/NewPosition")]
private void OnNewPosition(object sender, GPSEventArgs e)
{
lock( syncRoot )
{
State["GPSData"] = e.Data;
}
}
The form code on the other hand, simply has a couple of Labels to show the information received in the event. Note that it has also a controller declared in its main body with a special attribute [Controller] and then a subscription to changes in the state it manages. The controller is automatically created by CAB runtime the moment the form is added to the WorkItem:
public partial class BasicGPSInfoForm : Form
{
[Controller]
public GPSController controller;
public BasicGPSInfoForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
this.controller.State.StateChanged += new EventHandler<StateChangedEventArgs>(State_StateChanged);
}
void State_StateChanged(object sender, StateChangedEventArgs e)
{
this.label1.Invoke( new UpdateLabel(UpdateLabelMethod), " Long.:" + controller.gpsData.Longitude.ToString() + ", Lat.:" + this.controller.gpsData.Latitude.ToString(), this.controller.gpsData.rawString ); }
public delegate void UpdateLabel(string Text, string Raw);
void UpdateLabelMethod(string Text, string Raw)
{
label1.Text = Text;
label2.Text = Raw;
}
}
When state changes, the new information is written in the labels. The red code above is necessary because the need to marshal the call into the UI thread. (Something that hopefully will not be necessary in future versions of the CAB :-) )
At program startup, everything is wired up by adding the Form and the Listener to the GlobalWorkItem:
static void Main()
{
Application.EnableVisualStyles();
// Create the application host, add the main work item and launch it.
ApplicationHostFactory hostFactory = new ApplicationHostFactory();
ApplicationHost host = hostFactory.CreateHost();// Initialize the state
host.GlobalWorkItem.State["GPSData"] = new GPSData();// Get the GPS listener instance
IGPSListener listener = GPSListenerMock.Listener;//Add it to the container so it can wire up all the events
host.GlobalWorkItem.Add(listener);//Start listening and firing events
listener.Start();//Create the main form, add it to global work item and show it
BasicGPSInfoForm frm = new BasicGPSInfoForm();
host.GlobalWorkItem.Add(frm);
frm.ShowDialog();//When the for comes back, stop listener and close ports
listener.Stop();
}
That's it!
Comments
Anonymous
June 13, 2008
PingBack from http://sarah.wapnewsdigest.info/creatinggpslistener.htmlAnonymous
June 15, 2009
PingBack from http://workfromhomecareer.info/story.php?id=13839