A Data-Logging Flashlight


The next feature the customer wants to add to our ultimate flashlight is a data-logging facility. This will track the times when the flashlight has been switched on as well as the duration of each use. This tracking information will allow the performance of different batteries to be evaluated, and allows us to explore the manipulation of dates and times in the .NET Micro Framework. It will also let us see how we can build custom classes to store data, and how we can create a structure to hold a collection of data items.

Of course, the information is useless if it stays inside the flashlight, so we will need a way of transmitting the data to the outside world. Fortunately, the device we are using is fitted with an RS-232 connection, which we can use to transfer the data.

The following information will be stored:

  • The date and time at which the flashlight was turned on

  • The duration of the use of the flashlight

Creating a Data Storage Class

The .NET Framework (and also the .NET Micro Framework) provides a DateTime structure to manage the date and time and a TimeSpan structure to hold a given time interval. What we need to do is find a way of holding two of these items together in a lump that we can then store within the flashlight. The best way to do this is to create a class to hold the information.

 public class FlashlightUse {      public DateTime switchedOn;      public TimeSpan litDuration; } 

The FlashlightUse class holds information about a given use event. The time the flashlight was switched on and the duration for which it was lit are held in DateTime and TimeSpan structures. These are members of the class that provides the data payload.

The ToString Method in a Class

One other aspect of class design is giving classes behaviors that allow them to function with other classes in the system. There are a number of actions that an instance of a class should be able to perform, and one of them is to provide a textual description of its contents. In the case of our data-logging flashlight, this would make it possible to ask an instance of FlashlightUse to provide a string that gives the values it holds.

We do this by adding a ToString method to the class. This method overrides the method in the parent class.

 public override string ToString() {      return "On : " + switchedOn.ToString() + " for : " + litDuration.ToString(); } 

This ToString method returns a string of text giving the time the lamp was switched on and the duration information. Note that it makes use of the ToString methods in the two data members to produce the required output.

Class Constructors

A constructor is a method that is called when an instance of a class is being created. Constructor methods let us get control and set initial values for instances of our classes. If a class is given explicit constructor methods by the programmer, one of these methods must be called when an instance is created. In other words, we can force users of our classes to give us information when the class comes into being.

 public class FlashlightUse {      public DateTime switchedOn;      public TimeSpan litDuration;      public FlashlightUse(DateTime inSwitchedOn, DateTime inSwitchedOff) {      switchedOn = inSwitchedOn;      litDuration = inSwitchedOff.Subtract(switchedOn); }      public FlashlightUse(DateTime inSwitchedOn,TimeSpan inLitDuration)      {         switchedOn = inSwitchedOn;         litDuration = inLitDuration;      } } 

There are two ways by which we might want to create an instance of FlashlightUse. We might store the time that the flashlight was switched on and the time it was switched off. Alternatively, we might store the time the flashlight was switched on and the timespan giving the duration of its use. We can cater to both of these situations by providing two constructor methods that accept the appropriate parameters. This is a technique called overloading, by which a given action is implemented in a number of different ways. The idea is to make the use of our classes as easy and flexible as possible.

You can see the constructors in the preceding code; they are the methods with the same name as the class. They are called when the new keyword is used.

 FlashlightUse record = new FlashlightUse(switchedOn,DateTime.Now); 

This code will create an instance of the FlashlightUse class using two DateTime instances, that is, it will call the first of the two constructors created earlier in this section.

Constructors, Errors, and Exceptions

One of the reasons we use classes is that they can contain code that makes sure instances never contain invalid data. For example, in the case of our FlashlightUse class, we don't want to have any instances that contain a negative value for the duration the lamp was lit. If programmer error ever created any such instances, they would cause all our reading values to be incorrect. The solution to this seems simple: we just add to the constructor code that checks to see if the time interval we have been given is negative. However, the problem is that we have to do something if the constructor receives a negative value.

Normally, we can make a method refuse to use an invalid input and return a flag value indicating that a parameter is invalid, but a constructor cannot do this. The class instance exists as soon as a constructor completes. This means that the constructor must resort to drastic measures to signal a problem; it must throw an exception.

Exceptions in C#

C# uses exceptions to signal the occurrence of a very bad event. The normal flow of execution stops, and an exception instance is created that describes what has gone wrong. If the programmer does not provide an exception handler, the exception is caught by the runtime system and the execution thread ends.

Note 

In a .NET application running on a personal computer, an uncaught exception will cause the program to end and return control to the operating system. This will usually result in the display of an error message that the user can see and respond to. However, when a program is running inside an embedded device, as is the case with the .NET Micro Framework, an uncaught exception can be much more disastrous because there is no operating system for it to go back to. This means that you must be much more careful how you add exception handling when writing embedded code. You may also want to investigate watchdog technology, which enables part of your system to detect that another part is stuck and restart/reset it.

Throwing an Exception

Exceptions are thrown by using the throw keyword.

 public FlashlightUse(DateTime inSwitchedOn, TimeSpan inLitDuration) {    if (inLitDuration.Ticks < 0)    {       throw new Exception("Invalid FlashlightUse timespan");    }    switchedOn = inSwitchedOn;    litDuration = inLitDuration; } 

This version of the constructor uses the Ticks property of the TimeSpan structure. On the .NET Micro Framework, a tick is 100 nanoseconds. The Ticks property gives the length of the timespan in ticks of this duration. The constructor can test this value to determine whether the incoming duration is negative. If the duration value is found to be invalid, the constructor throws an exception with a message describing the error. With this test in place, it is impossible to create an instance of FlashlightUse that has a negative duration.

Note 

The decision about when to use an exception to signal an error is a difficult one. Using exceptions to handle errors that may occur frequently is not advisable. The best use of an exception is for an exceptional event from which recovery is not possible at the point at which the error occurs. A device not being present or being unable to provide a resource is reasonable grounds for throwing an exception. However, in the case of invalid user input, when it is possible to ask the user to reenter the data, an exception is not advisable.

Catching an Exception

If a method throws an exception, the execution is transferred to the nearest exception handler. If the programmer has not provided a handler, the exception is caught by the runtime system. To get control when an exception is thrown, you enclose a block of statements in a try-catch construction.

 try {      FlashlightUse record = new FlashlightUse(switchedOn,DateTime.Now);      Debug.Print("time record created OK"); } catch {      Debug.Print("time record creation failed"); } 

If the constructor throws an exception, the code in the catch block will run. If we want to retry the code in the try block, we must place the whole construction in a loop of some form, because once an exception has been thrown in a statement, the block containing it is abandoned.

Extracting Information from an Exception

The try-catch in the preceding section is the simplest form of construction. It is also possible to get hold of the exception that has been thrown and inspect it for details about what went wrong.

 try {      FlashlightUse record = new FlashlightUse(switchedOn,DateTime.Now); } catch (Exception e) {      Debug.Print("Time record creation failed: " + e.Message); } 

By explicitly catching the exception rather than just handling it, we can obtain the message that we created when the exception was thrown. This code would print the following when the instance construction fails:

 Time record creation failed: Invalid FlashlightUse timespan 

The exception also holds other information, including a stack trace that can be used to find out what was happening when the exception was thrown.

Exceptions and the Finally Block

An exception causes our program to branch to the code in the catch clause. This will then handle the exception and transfer execution to another part of the program, perhaps by performing a return or a break out of the current code. However, frequently, things need to happen whether an exception is thrown or not. An example of this would be the use of a particular device. Whatever happens during the use of that device, you want to execute the code that closes the device so that the code can be used in other parts of the program.

Place this code in the finally block.

 try {    // code that connects to the device here } catch {    // code that handles any exceptions } finally {    // code that tidies up here    // executed whether or not an exception is thrown    // and irrespective of what happens in the try and    // catch blocks } 

Whatever happens in the try and catch blocks (even if they use a return to exit the method in which the code is running), the code in the finally block will run. The catch clause can be left out if you want to ensure that only a resource is released.

If you are wondering why you need finally when the program will perform the statement following the try-catch construction anyway, consider the following:

 static void exceptionFun() {    try    {       Debug.Print("Inside the try block");       return;    }    finally    {       Debug.Print("Inside the finally clause");    } } 

In this code, the method exceptionFun contains a try-catch construction. Within the body of the method, a return statement will cause execution to leave the method. However, the finally clause executes before the return executes. In other words, if the method executes, it will print.

 Inside the try block Inside the finally clause 

The finally clause executes, irrespective of anything else.

Exceptions and Design

The way in which error handling is performed in a system is best decided at the start of the development. At the very beginning, you should plan how to generate and propagate exceptions through your system, along with a means to test each of the exceptions.

Note 

It is often the case that code to release a resource the statements running in the finally block, may also throw exceptions. This means that there will be a need for a try-catch construction in these statements, too.

One of the most important aspects of any embedded system is the way it responds in such situations. Error management should be integrated into the program at design time, as should test code that throws the exceptions, to ensure that the device behaves correctly.

Logging the Data

Now we need to add the code that performs the logging. When the flashlight is turned on, the program must record the time, and then when the flashlight is turned off, the program must create an instance of the FlashLightUse class to record this. The software must hold a collection of these log items for retrieval later for analysis.

Creating an Array

One way to log the data would be to create an array of FlashLightUse instances held in the memory of the flashlight:

 static private FlashlightUse[] FlashlightUseLog; 

This declares a reference that can refer to array instances. At the moment, we have not created an array; we have just created a tag that can refer to an array instance. We must use new to create the actual array.

 FlashlightUseLog = new FlashlightUse[1000]; 

This statement creates an array instance that can hold references to 1000 objects of type FlashlightUse. However, just as with the reference to the array at the beginning of this section, we have not yet created any log item instances, just an array that contains references that can point at instances. We can now go ahead and make an actual reading instance.

 FlashLightUseLog[0] = new FlashlightUse(switchedOn, DateTime.Now); 

This code creates a log item instance and then sets the element at the start of the array to refer to it.

Figure 4-4 shows the effect of the preceding code. The array reference refers to an array instance that contains 1,000 elements, each of which is a reference that can refer to an instance of the FlashlightUse class. The element with subscript 0 (that is, the one at the start of the array) refers to an actual log instance. The remaining 999 references do not refer to anything. In a C# program, this value is called null.

image from book
Figure 4-4: Filling up an array of references.

Storing Log Records in an Array

When the program needs to add another log item to the array, it can search for an element that contains a null reference and then set that to refer to the new log item.

 private static bool storeUseRecord(FlashlightUse useLog) {    for ( int i = 0 ; i < FlashLightUseLog.Length ; i++ )    {       if ( FlashLightUseLog[i] == null )       {          FlashLightUseLog[i] = useLog;          return true;       }    }    return false; } 

The method storeUseRecord receives a parameter that is the reference to the log item to be stored. It searches for an entry in the array that is set to null, and if it finds one, it makes that entry refer to the log item. If the method reaches the end of the array without finding a null entry, the method is not able to store the data and returns false to indicate that it has failed.

Note 

A more efficient approach would be to store the index of the next empty position and update this as the array fills up.

When we want to retrieve the records from the array, we can search for all the non-null elements in the array.

 static void dumpLog() {    for (int i = 0; i < FlashLightUseLog.Length; i++)    {        if (FlashLightUseLog[i] == null)        {           continue;        }        Debug.Print(FlashLightUseLog[i].ToString());    } } 

The method dumpLog uses a for loop to work through the array and print out each of the non-null elements. An array instance uses a property called Length, which gives the number of elements in the array. This code will therefore work for any array size. Note that the C# language allows us to declare a control variable directly inside the for loop construction.

Storing Log Records in an ArrayList

If we know in advance exactly how many records we need to save, an array will work well. However, we might require more flexibility so that size of the storage used increases with the number of records to be held. C# also provides a collection class called an ArrayList that can be used in this situation. Rather than having to specify the storage size at the start of the program, an ArrayList will grow as the program adds more data. The ArrayList class is in the System.Collections namespace, so if we wish to use it without giving the fully qualified name each time, we must add a using directive to ask the compiler to check this namespace each time we identify a class.

 using System.Collections; 

Now we can create an instance of an ArrayList.

 private static ArrayList UseLog = new ArrayList(); 

This code creates an instance of an ArrayList and sets the reference UseLog to refer to it. Note that, at the moment, the ArrayList does not contain any references to log instances, but we can add instances by calling the Add method.

 FlashlightUse record; record = new FlashlightUse(switchedOn, DateTime.Now); UseLog.Add(record); 

The record is added to the end of the ArrayList collection. Note that we do not need to manage this addition; if the storage needs to be extended, the extension will happen automatically.

When we want to retrieve the data from ArrayList, we can work through it in exactly the same way we worked through the array.

 for (int i = 0; i < UseLog.Count; i++) {    if (UseLog[i] == null)    {       continue;    }    Debug.Print(FlashLightUseLog[i].ToString()); } 

This code is very similar to the loop that works through the array. The ArrayList provides an indexer mechanism whereby users of the collection can provide a subscript to identify the item that is to be returned. In this context, the ArrayList behaves exactly as an array. There is one difference between the use of the two types, though: an ArrayList uses a property named Count that gives the number of items in the list, whereas the Length property of an array does not.

ArrayLists and Data Types

When you create an array, you can give the elements in it a particular type. The compiler will then make sure that you store only elements of that type in the array. Earlier we made a FlashlightUse array, which holds references to FlashlightUse instances. If we add code that stores a reference to any other type in the array, the compiler will refuse to compile the code. During the execution of the code, the runtime system also checks that references are used appropriately, so within the .NET Micro Framework a class will never be used in a context in which it would be invalid to do so.

However, the ArrayList collection needs to be able to hold references to any class. It does this by managing its contents using references to the Object class. Object is the parent of all classes, which means that a reference that is able to refer to Object can also refer to any class that is derived from it. Thus, when we retrieve a reference from the collection, what we actually get is a reference to an instance of an object. The problem with this is that we may wish to use the object as it really is; in this case, an instance of the FlashlightUse class. As an example, we may wish to print out a list of all the times that the flashlight was switched on.

 for (int i = 0; i < UseLog.Count; i++) {    if (UseLog[i] == null)    {       continue;    }    FlashlightUse use = (FlashlightUse) UseLog [i];    Debug.Print(use.switchedOn.ToString()); } 

We need to declare a reference to the appropriate type and then use a cast to set this to refer to the element in the ArrayList. We can then retrieve the property using this reference. This code will work correctly only if the ArrayList actually contains references of the correct type. If an attempt is made to use the cast to convert an element of an incorrect type, the program will fail at runtime by throwing an exception.

A C# program can check the type of a reference by using the operator is.

 if (UseLog[i] is FlashlightUse) {    FlashlightUse use = (FlashlightUse)UseLog[i];    Debug.Print(use.switchedOn.ToString()); } 

This code will attempt to perform the cast only if it works correctly.

If you are sure that an ArrayList collection contains only references of the correct type, you can simplify the code to extract the items from the collection by using the foreach construction.

 foreach (FlashlightUse logItem in UseLog) {    Debug.Print(logItem.switchedOn.ToString()); } 

This construction will iterate through a collection and perform the loop with the reference logItem being set to each item in turn.

Note 

You can use an ArrayList to hold data in this way, but if the program is ever interrupted or the device powered down, the information will be lost because it is held in the program memory. For logging data that is retained even when the device is switched off, you need to use nonvolatile memory that retains its contents in this situation. We will discuss the use of this in Chapter 5, "Developing for the .NET Micro Framework," in the section addressing weak references.

Exporting Data via the RS-232 Serial Port

We now have a flashlight that can store data for us. However, we have no way of getting the logged data out of the flashlight and into another system for analysis. Fortunately, the flashlight has been fitted with an RS-232 serial port that can be used for input and output. When the flashlight software receives a request, it can then transmit the contents of the log to a connected device via this port.

Configuring and Creating an RS-232 Serial Port Instance

A physical serial port is managed by an instance of the SerialPort class. This provides methods that can be used to write a stream of bytes to the port and read incoming data. The constructor for the class is provided with a reference to an instance of the SerialPort.Configuration class, which is used to set the baud rate, the physical device to be used, and whether the port is to perform handshaking.

 SerialPort.Configuration config; config = new SerialPort.Configuration(    SerialPort.Serial.COM2,    SerialPort.BaudRate.Baud9600,    false); // no flow control 

This code creates an instance of the SerialPort.Configuration class, which selects the second serial port and sets it to 9600 baud with no flow control. We can now create the serial port itself.

 SerialPort port; port = new SerialPort(config); 

We can now call methods on the serial port to receive and transmit data.

Transmitting Data to an RS-232 Serial Port

The serial port provides a Write method that will send information to a remote device. The information to be sent is supplied to the Write method as an array of bytes, a start position in the array, and a number of bytes to transfer. However, the ToString method of LogItem supplies the information about a log event as a string. The program must convert this string into an array of bytes. The .NET Micro Framework provides a utility method to perform this conversion. The Encoding.UEF8.GetBytes method is supplied with a string and returns a byte array.

 static void exportData() {    foreach (FlashlightUse logItem in UseLog)    {       byte[] buffer = Encoding.UTF8.GetBytes(logItem.ToString());       port.Write(          buffer, // data source          0, // start position in buffer          buffer.Length// number of byes to write       );    }    port.Flush(); } 

Note 

The preceding code works through the log items and sends each out in turn. The Write method receives a reference to the byte array, the start position in the buffer to write from, and the number of bytes that are to be written. Once the bytes have been written, the code calls the Flush method to remove any that might be left in the buffer for the port. The code is in the form of a method called exportData. This code does not provide any means by which the received data could be checked for validity. Bearing in mind that serial data travels down cables that could be susceptible to electrical noise and interference, a production system should probably use a checksum of some kind to ensure that no damage occurs to the information during transmission.

Receiving Data from a Serial Port

Now that we can send serial data out of the serial port, we might need a way for the remote device to trigger the transmission. The serial port provides a method named Read that will fetch a number of characters from the serial port.

 byte[] buffer = new byte[1]; int count = port.Read(    buffer, // destination    0, // start position    1, // 1 byte    1000 // wait 1 second ); 

The Read method receives a reference to the byte buffer that will hold the received characters, along with the start position in the buffer and the number of characters that are to be read. It also receives a timeout value, given in milliseconds. A call to the Read method will block the thread that makes the call until either the requested number of bytes have arrived or the timeout expires. The code will pause until a character arrives or a second passes. The Read value returns the number of bytes that have been read, and we can test this to see if anything has arrived at the serial port.

 private static void monitorLogRequests() {    byte[] buffer = new byte[1];    while (true)    {       int count = port.Read(          buffer, // destination          0, // start position          1, // 1 byte          1000 // wait 1 second       );       if (count > 0)       {          if (buffer[0] == 'D')          {             exportData();          }       }    } } 

The method monitorLogRequests repeatedly reads the serial port waiting for a single character to arrive. If it receives a character and the character is D, it calls the exportData method to transmit the information.

The best way to ensure that the log data is sent out on request is to create another thread.

 System.Threading.Thread serialThread = new System.Threading.Thread(monitorLogRequests); 

This thread will run the method so that whenever the flashlight receives the character D, it will export the data.

Note 

Some issues might need to be considered with regard to threading in situations like this. If the flashlight is used while the log is being sent to the serial port, the contents of ArrayList, which holds the log items, will change. This may not be a problem, in that new items are simply added to the end of the collection. However, if another thread was removing items from the ArrayList as they were being written out, this could cause problems. In this situation, you will need to consider the use of Monitors to properly manage the sequence of the interactions. If you do this, you must also give thought to the effect of pausing threads while others finish; for example, the serial output process may take some considerable time, particularly if you allow the receiving device to use flow control. In this situation, you would not want the flashlight to refuse to switch on until a log dump has completed.




Embedded Programming with the Microsoft .Net Micro Framework
Embedded Programming with the Microsoft .NET Micro Framework
ISBN: 0735623651
EAN: 2147483647
Year: 2007
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net