Serial Communications in a Windows Service

The following article is based on information that I gleaned from these two sources: Jayesh Jain’s article at: http://www.developerfusion.co.uk/show/3441/2/ and the MSDN article Introduction to Windows Service Applications at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbconintroductiontontserviceapplications.asp

Windows Services

Originally called a NT service, the core function of a Windows service is to run an application in the background. There are few things that make them different from a Windows application. A Windows service can start before any user logs in to the system (if it has been setup to start at start up process). A Windows service can also be setup in such a way that it requires a user to start it manually.

Windows services are processes and run quite efficiently. Normally a Windows service will not have a user interface for the simple reason that it can be run even if no one is logged into the system. This is not a rule, just good design.

In Windows XP you can view a list of services currently running on your computer by opening Control Panel / Administrative Tools / Services, as shown here:

Creating a Windows Service in VB .NET 2005

Before .NET, creating a Windows service was hard. Thanks to .NET, however, this is becoming very easy and we shall now learn how to create a Windows Service in VB.NET.  The steps outlined here are for VB 2005 (Visual Studio 2005), but you might do this with Visual Studio 2003 by employing DesktopSerialIO.dll, instead of the VS2005 SerialPort component.  Note, the source code for this project is included on the CD ROM that accompanies my book.

There are a few things that you should know before we dive in, however. Windows services are not available under Windows 95, 98 or ME -- you need to have Windows NT 4.0, Windows 200x and Windows XP to run services.

The advantage to use .NET is that the framework incorporates all of the classes to create, install and control a Windows Service. Open Visual Studio .NET and create a new Windows service project, which we shall call "GPSLocationService". Click OK.   Our GPSLocationService service will do these things:

·          Automatically detect a GPS receiver, and open the port associated with the receiver (note, the GPS receiver port will not be available to other applications if this service is running).

·          Log Date, Time, and valid Latitude and Longitude to the EventLog.

 

Add a serial port control from the Toolbox.  Add a reference to DecodeGPS.dll.  Import this needed references as shown in the Source Code section.  Add two Timer controls.  You will need to add these to the Toolbox – the Timer control that is already there is not the one that you need; this is a bug!  Instead, add a System.Timers.Timer and drop two on the design surface.  The Windows.Forms.Timer that already is in the Toolbox will not work in a Service.

Hint: Use of one or more System.Timers.Timer timers is a common feature in a Windows Service.  Services execute in the background and usually require monitoring of PC data or events at regular intervals.  Timers are a naturally way to drive such processes.

Source Code

Double click the design surface to open the code window.  Type in the following declarations at the top of the Service1 Class:

    Private WithEvents GPSDecoder As New DecodeGPS.DecodeGPS

    Private GPSFound As Boolean

    Private GPSPort As String

    Private PortIndex As Integer

    Private SerialPortNames() As String

    Private StartUp As Boolean = True

    Private FirstLog As Boolean = True

Go to the OnStart event subroutine.  Type in the following code:

        With SerialPort1

            .StopBits = 1

            .Parity = IO.Ports.Parity.None

            .BaudRate = 4800

            .RtsEnable = True

            .DtrEnable = True

            SerialPortNames = .GetPortNames()

        End With

        'prepare to locate a gps receiver

        Timer1.Interval = 4000

        Timer2.Interval = 300

        Timer2.Enabled = False

        Timer1.Enabled = True

Go to the code window for the Timer1_Elapsed event subroutine. Type in the following code:

        Dim MyLog As New EventLog() ' create a new event log

        ' Check if the the Event Log Exists

        If Not MyLog.SourceExists("GPSLocationService") Then

            MyLog.CreateEventSource("GPSLocationService", "GPS Log") ' Create Log

        End If

        MyLog.Source = "GPSLocationService"

        ' Write to the Log

        If StartUp = True Then

            StartUp = False

            For I As Integer = 0 To SerialPortNames.Length - 1

                ' Write to the Log

                MyLog.WriteEntry("GPS Log" & " " & _

                " Serial port found at " & SerialPortNames(I) & vbCrLf, EventLogEntryType.Information)

            Next

        End If

        If GPSFound = False Then

            If PortIndex = 0 Then

                MyLog.Source = "GPSLocationService"

                ' Write to the Log

                MyLog.WriteEntry("GPS Log" & " " & _

                CStr(TimeOfDay) & " GPS Loc Started (No GPS identified) ", EventLogEntryType.Information)

            End If

            If PortIndex < SerialPortNames.Length Then

                With SerialPort1

                    Timer2.Enabled = False

                    If .IsOpen = True Then

                        .Close()

                    End If

                    .PortName = SerialPortNames(PortIndex)

                    PortIndex += 1

                    Try

                        .Open()

                    Catch ex As Exception

                    End Try

                    If .IsOpen = True Then Timer2.Enabled = True

                End With

            Else

                MyLog.Source = "GPSLocationService"

                ' Write to the Log

                MyLog.WriteEntry("GPS Log" & " " & _

                CStr(TimeOfDay) & " A GPS Receiver was not detected! ", EventLogEntryType.Information)

                Timer1.Enabled = False

            End If

        Else

            MyLog.Source = "GPSLocationService"

            ' Write to the Log

            With GPSDecoder

                If .status = "A" Then

                    MyLog.WriteEntry("GPS Log" & " " & _

                    .DecimalLatitude.ToString & ", " & .DecimalLongitude.ToString & ", " _

                    & .LocalDate & ", " & .LocalTime, EventLogEntryType.Information)

                Else

                    MyLog.WriteEntry("GPS Log" & " " & _

                    CStr(TimeOfDay) & " GPS Coordinates not valid! ", EventLogEntryType.Information)

                End If

            End With

        End If

Add the following code to the Timer2_Elapsed event:

        Dim Buffer As String

        With SerialPort1

            If .IsOpen = True AndAlso .BytesToRead > 0 Then

                Buffer = .ReadExisting

                Try

                    SyncLock (Me)

                        GPSDecoder.GPSStream(Buffer)

                    End SyncLock

                Catch ex As Exception

                End Try

            End If

        End With

Add this code to the GPSDecoder_GPSDecoded event subroutine:

        GPSFound = True

        GPSPort = SerialPort1.PortName

Type in the following code in the OnStop event subroutine:

        Timer1.Enabled = False

        Timer2.Enabled = False

        If SerialPort1.IsOpen Then SerialPort1.Close()

Our application is now ready, but there are a few things that we need to do before we build it. This EXE is not a Windows application, and hence you can't just click and run it -- it needs to be installed as a service. Visual Studio has a facility where we can add an installer to our program and then use a utility program or Setup program to install the service.

There is a Design Issue that needs to be addressed! (future)

What happens if the GPS receiver is disconnected, a common occurrence with USB devices, after the service has detected the port and begun running?  There is no code to handle this problem.  Many, perhaps most, Windows services have a common theme, and this example is no exception.  Often, at regular intervals, they monitor ongoing operations and then do something useful.  So, frequently you see some sort of timing mechanism (the Sytem.Timers.Timer control) to perform this regular monitor.  We do that now, to receive the serial NMEA-0183 data stream.  However, if data stops (for whatever reason), we do not detect that fact.  What might be a useful way to handle this here?  I’ll outline some steps:

·          Set a Boolean class-level flag to False at the top of the GPS_Decoded event.  This flag indicates that, for this cycle, GPS data have been decoded.

·          Test this same flag in the Timer1_Elapsed event, inside the GPSFound = True block.  If GPSFound = True, and our newly added flag is False, then the process is running correctly, and the GPS receive still is connected.  We then set this new flag = True, so that the test is ready for the next Timer1_Elapsed test cycle.

·          If GPSFound = True, and our newly added flag is True, then the GPS receiver no longer is connected (or otherwise has stopped outputting data).  We then can execute code to perform some sort of remedial action, perhaps to log the fact and to start another timer that attempts to reestablish connection to a GPS receiver at regular intervals – or that notifies the system user via the System Tray or some sort of popup window (see Suggestions, below).

Adding an Installer to the Project

·           

·          In Solution Explorer, double-click GPSLocationService1.vb.

 

·          Right-click the design surface, choose Properties, and then click Add Installer.

·           

·          In the Properties dialog box for ServiceInstaller1, change the ServiceName  and DisplayName properties to GPSLocationService.

·          In Design view, click ServiceProcessInstaller1.

·          In the Properties dialog box, change the Account property to LocalSystem. The LocalService value and the NetworkService value are only available in Microsoft Windows XP and later operating systems.

 

 

Build the Service

Use the Build menu item and select Build GPSLocationService.

Use a compiled Setup project to install the Windows Service

After you complete the steps in the preceding section to configure the Windows Service project, you can do one of two things. 

First, you might open a Command Prompt or use the Run window to execute InstlUtil.exe with our service as a command-line argument.  InstlUtil.exe is installed with the .NET SDK.  On my computer it is located in C:\windows\Microsoft.NET\Framework\v2.0.50727\.  At the command prompt type:

C:\windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe  c:\SVCTest\GPSLocationService\bin\Release\GPSLocationService.EXE  (all one line)

When you execute this statement, our service will be installed.  I find this first procedure to be best for two reasons.  It is easy, but more importantly, it works.  The next procedure is what is suggested by Microsoft – and it should be best… However, I simply haven’t been able to get it to work reliably.  However, you may want to give it a try.

The second technique is to add a deployment project that packages the service application so that the service application can be installed. To do this, follow these steps:

1.

Add a new project to your GPSLocationService project.

a.

Go to the File / Add / New Project menu item.

b.

Under Other Project Types, click Setup and Deployment.

c.

Under Templates, click Setup Project.

d.

In the Name box, type GPSServiceSetup.

 

 

2.

Tell the deployment project what the deployment project will package.

a.

In Solution Explorer, right-click GPSServiceSetup, point to Add, and then click Project Output.

b.

In the Add Project Output Group dialog box, click GPSLocationService in the Project box.

c.

Click Primary Output, and then click OK.

 

 

 

3.

By default, Setup projects are not included in the build configuration. To build the solution, follow these steps:

a.

Use one of the following methods:

Right-click GPSLocationService, and then click Build. Then, right-click GPSServiceSetup, and then click Build.

To build the whole solution at the same time, click Configuration Manager on the Build menu, and then click to select the Build check box for GPSServiceSetup.

 

b.

Press CTRL+SHIFT+B to build the whole solution. When the solution is built, you have a complete Setup package for the service.

 

4.

To install the service, right-click GPSServiceSetup, and then click Install.  You will see dialog:

 

 

 

 

5.

In the GPSServiceSetup dialog box, click Next three times. Notice that a progress bar appears while the Setup program is installing the service.

6.

When the service is installed, click Close.

Starting the service

Running a service and starting a service are two different things -- when you install the service you are running the service, but have yet have to start it.

To view and start the service, open Control Panel / Administrative Tools. Now click Services, locate GPSLocationService, right click on it and select Start to start it:

Our service is now started. Open the Event Viewer from Administrative Tools and click Application Log to see the logs created by the Service (GPSLocationService) every 4 seconds. If you don't see any logs click refresh (F5). You will have to keep refreshing to see the latest event logs:

Double-click the event to view the detail window:

 

Stopping the Service

This procedure is similar to installing the service but now we shall run the InstallUtil with the /U parameter which will uninstall the service:

C:\windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe /u c:\SVCTest\GPSLocationService\bin\Release\GPSLocationService.EXE  (all one line)

 (Specify the executable path on your computer.)

Take note of the message to confirm that the service was uninstalled properly and verify this using the Services window under Control Panel/Administrative Tools.

If you used the GPSServiceSetup to create a Setup MSI, then you may use that Setup to uninstall the service.

Suggestions