.NET can extend its influence beyond the reaches of your keyboard. The .NET remoting library is a well-architected library with some sophisticated internal plumbing allowing you to connect to assemblies remotely, and transparently over a network. In earlier versions of Windows, remoting was accomplished with that sibling of COM known as DCOM (Distributed COM). You could also communicate remotely using various third party ActiveX controls (and still can). The .NET remoting architecture, however, makes it so simple to create remote applications that it will have you wondering what you might be missing when you are finished.
Figure 1 - Remote Sensing Satellite for Oceanographic Research
In this article we will revisit the GP-3 board (which we have employed in a few other hardware projects on C# Corner) and use the GP-3 to measure temperature in a remote location. The temperature from the GP-3 will be queried over TCPIP to our client location. We will also sound an alarm over the internet through the GP-3 if the temperature falls below freezing.
A little bit about the Remote Architecture
The .NET remoting library is actually quite extensive including capability for http, ftp, and tcp. The part of the .NET remoting architecture that we will be using in this article takes advantage of tcp communication and consists of two pieces, a server piece that reads the temperature at the location we are interested, and a client piece that will remotely access the temperature.
Figure 2 - Client/Server Remoting Architecture
The client piece in our application is a Windows Form that contains a timer that requests a temperature reading every 5 seconds. It uses a proxy to call the remote object on the server side and the call is made as if the remote object were living on the same machine as the client. Another words, the call to the remote object is transparent to the client. The client can call on any method of the remote object that is publicly visible. The remote object in our example takes the form of a instance of a class. The servers job is simply to register the the remote object and a channel to the remote object so that the client knows about them. The Remote Object does all the important work by controlling and sensing the hardware through the GP3 board. To get a little more detailed idea of the architecture for the current project, take a look at the UML diagram in figure 3 showing the three individual assemblies needed to complete the remoting picture. Note that the Client piece (on the left side) lives on the client machine and the remote object and the server piece live on the server machine.
Figure 3 - UML Diagram of the temperature sensing system reverse engineered using WithClass
Sensing Temperature
Figure 4 - GP-3 Board interfaced to temperature sensing circuitry and the serial port of the computer
Before we go any further, let's get a look at how we measure temperature. There are a few devices used to measure temperature electronically. The one we picked can be picked up in Radio Shack for a couple of bucks and is known as a thermistor. The thermistor has the uncanny behavior of increasing its resistance as the temperature drops. At -50 degrees Celsius the thermistor measures about 320K ohm and at 110 degrees Celsius it measures about 750 ohm. Room temperature gives us a resistance reading of about 10K ohm. By know means is this a linear scale so we need to interpolate between values listed on the back of the Radio Shack thermistor packaging to determine our temperature.
We can measure the resistance one of two ways using the GP3. One way is to put a capacitor across the resistor and determine what is known as the RC time (Resistance-Capacitance time). The way this works is that it takes a certain determinable amount of time for a capacitor to become charged and that time is determined by the product of the resistance into the charging capacitor and the capacitance of the capacitor. If we use our thermistor as the R and a known capacitance (say .01 microfarads), we can determine the unknown resistance of the thermistor by dividing the time it takes to charge by the capacitance and a constant. The formula is shown below:
time to charge = R thermistor .693 * C |
|
Figure 5 - Measuring the thermistor resistance using the RC time
It so happens that the PIC microcontroller on the GP-3 board has a command called rctime that allows us to measure how long it takes to charge the capacitor across the thermistor.
Another method for determining the resistance of the thermistor (and the one we chose) is simply to put a known voltage across a divider circuit and measure the voltage drop. A divider circuit consists of the thermistor and a known resistor value in series with a known voltage across the circuit. The formula for determining the resistance of the thermistor across the divider is shown below:
(voltage)( 1000 ohms) = Rthermistor (5 - voltage) |
|
Figure 6 - Measuring the thermistor resistance using the divider voltage
Once we've calculated the resistance of the thermistor, we can do a table look up from the specifications of the thermistor to determine the corresponding temperature. If the resistance measured does not fall exactly in the table, we simply interpolate between the two closest values.
Sounding the Alarm
Our particular client wants to warn the people in the remote area when the temperature drops below freezing (These days in Manhattan, just below freezing is a welcome balmy day), but the people on the remote site have certain sensitive equipment that cannot withstand freezing temperatures for long. The client monitoring the remote site will trigger the alarm over tcpip to the remote object so that people can shutdown their temperature sensitive equipment. Below is the circuit hooked to the GP-3 board to set off the alarm. The alarm also comes with a kill switch that will shut off the annoying buzzing sound when the remote users have been made aware of the temperature-sensitive problem. If you want your alarm to be louder, you can attach an opamp circuit (such as an LM386) to the alarm in order to amplify the sound.
Figure 7 - Alarm and kill switch circuits controlled through the GP-3
The .NET Code
Now we are ready to take a look at the .NET code involved in running this remote system. As stated earlier, our .NET solution consists of three assemblies: a client, a server, and a remote object. First we will examine the remote object piece which talks directly to the GP-3 board and through a proxy to our client.
Remote Object
The remote object takes the form of a class library project and compiles into a dll. The assembly consists of a single class that inherits from the MarshalByRefObject class. This inheritance mechanism helps set up our remote object to communicate across application domains. Other than inheriting from MarshalByRefObject class, there is not much more we have to do to the class to enable its remoting capabilities. We simply fill in the methods and properties we want our other application domains to access. Below is the method GetCurrentTemperature for reading the temperature off the divider circuit. This is the method we will call directly from our remote client to read temperature from the GP-3 board.
Listing 1 - Measuring the temperature of the thermistor
public double GetCurrentTemperature()
{
// send voltage across the thermistor divider circuit
gp3.high(1);
// get a/d voltage reading across divider
long tmp1 = gp3.a2d(0);
// pause 200 milliseconds to debounce
Pause(2);
// convert the a/d reading from a count to a voltage
float voltage = ((float)tmp1 * 5.0f/ 1024.0f);
// calculate thermistor resistance from voltage across the thermistor
// therm /(1k + therm) * 5 V = voltage
// therm = (voltage * 1000 ohms)/(5 volts - voltage);
float therm = (voltage * 1000) / (5 - voltage);
// convert to a celcius temperature using the chart
double temperature = FindClosestTemperature(therm);
// convert to fahrenheit
temperature = (temperature * 9/5) + 32;
Console.WriteLine("{0}, reading = {1}", temperature, voltage);
// stop current flow across the divider to save the battery
gp3.low(1);
return temperature;
}
The alarm is also sounded in the remote object through the GP-3 board. The SoundAlarm method takes advantage of the PIC's freq command that allows you to send a specific oscillating frequency voltage wave for specific duration through the speaker alarm. The alarm is sounded continually until the pushbutton is pressed. The pushbutton state is polled by input pin RB-3 on the GP-3 board. When this pin goes high, the while loop is terminated.
Listing 2 - Sound the Alarm until the pushbutton is pressed
public void SoundAlarm()
{
Console.WriteLine("*** Sounding Alarm ***");
// set the value of the loop to zero to force it go
// through the alarm loop at least once
int val = 0;
// read the initial value of the pushbutton
int x = gp3.inp(3);
x = gp3.inp(3);
Pause(5);
// Loop and sound the alarm until the pushbutton is pressed
while (val == 0)
{
// sound a chirp in the alarm for 1/2 second at 6 KHz
gp3.freq(5, 6000, 500);
Pause(10);
// add the value of the pushbutton input to val
// (high == 1, low == 0)
val += gp3.inp(3);
// terminate the loop if the pushbutton is pressed
if (val > 0)
break;
// sound a chirp in the alarm for 1/2 second at 6 KHz
gp3.freq(5, 6000, 500);
Pause(10);
val += gp3.inp(3);
// terminate the loop if the pushbutton is pressed
if (val > 0)
break;
// sound a chirp in the alarm for 1/2 second at 6 KHz
gp3.freq(5, 6000, 500);
Pause(10);
val += gp3.inp(3);
// terminate the loop if the pushbutton is pressed
if (val > 0)
break;
}
Console.WriteLine("----- Alarm Terminated -----");
}
The Server
The server assembly is a simple .NET console application. All it does is register the channel and the remote object. Servers manage remote objects in two flavors (single call and singleton). Single call remote objects set up the remote object so that after a method call is made to it, the remote object is released. This is good when you don't want to maintain the state of your remote object after the call. But what if you want the remote object to remember things from the previous time you used it. Then you would manage the remote object as a singleton (as in the case of our temperature remote object). The singleton remote object sits around waiting for calls from a client. Care needs to be taken with the singleton remote object when you have several clients calling it at once. In the case of our remote temperature measuring object, we only have one client, so we don't have to synchronize calls to it. Listing 3 shows the server class that registers our remote object as a singleton on port 1234:
Listing 3 - Server Class for our remote temperature system
namespace RemoteTemperatureServer
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class Server
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// Set up the channel on port 1234 and register it
TcpServerChannel channel = new TcpServerChannel(1234);
ChannelServices.RegisterChannel(channel);
// Register the remote object in singleton mode
RemotingConfiguration.RegisterWellKnownServiceType
(typeof(RemoteTemperatureObject.RemoteObject),
"RemoteTemperatureObject",
WellKnownObjectMode.Singleton);
Console.WriteLine("Press enter to terminate server...");
Console.ReadLine();
}
}
}
The Client Application
Figure 8 - Client Application displaying the remote temperature
The final piece of the project is the client. This application consists of a Windows Form that reflects the temperature in a graphical thermometer we lifted from one of my previous articles called Web Thermometer in C# and .NET. The client is set up to handle remoting through a configuration file shown in listing 4 below. The configuration lists the tcp address on the server in which to locate the remote temperature object. The url attribute in the <wellknown type> flag contains the tcp address of where to locate the server.
Listing 4 - The remote temperature client configuration file
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown type = "RemoteTemperatureObject, RemoteTemperatureServer" url = "tcp://localhost:1234/RemoteTemperatureObject" />
</client>
<channels>
<channel ref="tcp client" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
The code for loading the configuration file is shown in listing 5. This code automatically registers the channel on the client side using the information in the configuration file. Note that you have the option of executing similar code for reading the configuration on the server side as well:
Listing 5 - Configure the connection to the remote object on the client side
public void LinkToServer()
{
// read the configuration file and register the connection on the client side
RemotingConfiguration.Configure("remotetemperature.config");
}
Now that we've set up our communication we need to try to communicate with the remote object. This is done by the client every 5 seconds in the timer event handler. The event handler calls RetrieveTemperature which in turn attempts to activate the remote object and call GetCurrentTemperature through the proxy to retrieve the temperature from the GP-3 board on the server side. If the temperature read falls below freezing, the client will attempt to activate the alarm through the remote object's SoundAlarm method.
Listing 6 - Activating the remote object and retrieving the temperature from the client side
private void RetrieveTemperature()
{
// get the url entry from the configuration collection of well know client types
// (which was previously read from the configuration file)
WellKnownClientTypeEntry[] entries = RemotingConfiguration.GetRegisteredWellKnownClientTypes();
// get the first url entry which points to the server
string url = entries[0].ObjectUrl;
// Activate the remote object
_remoteTemperatureReader = (RemoteTemperatureObject.RemoteObject)Activator.GetObject(typeof(RemoteTemperatureObject.RemoteObject), url);
try
{
// call GetCurrentTemperature remotely through the proxy
double theTemperature = _remoteTemperatureReader.GetCurrentTemperature();
// Set the graphical thermometer to the remotely read temperature
this.thermometer1.Temperature = theTemperature;
// check for temperatures below 32 degrees (freezing).
// if temperature drops below 32, sound the alarm remotely
if (theTemperature < 32)
{
thermometer1.Alarm = true;
_remoteTemperatureReader.SoundAlarm();
timer1.Stop();
}
}
catch (Exception ex)
{
timer1.Stop();
MessageBox.Show(ex.Message.ToString() + "\n " + ex.StackTrace.ToString());
Console.WriteLine(ex.Message.ToString() + "\n " + ex.StackTrace.ToString());
}
}
Running the Application
I wanted to mention a few things about running the remote system. The server needs to be launched first so that the client can communicate with it. Once the server is running, it sits and waits for a client to connect to it. Running the client application begins the temperature monitoring process.
Figure 9 - Server Console Screen
Conclusion
The power of using the internet to control and sense remote applications opens you up to a host of applications. You could create a web cam security application, a water level monitoring system, or a email alert system to name a few. With the growing popularity of wireless internet, you could even control and sense devices from mobile platforms like your car.
If you want to try this experiment yourself you can get a hold of the gp-3 board at the AWC website. The cost of the kit is $39.95. The other parts for the experiment (the thermistor, resistors, capacitor, pushbutton and piezo alarm) can all be purchased at Radio Shack.