Windows 7 Saving energy with the .NET Micro Framework


Extraordinary Robot
News Feed
Our home is equipped with a relatively old gas heater, built in 1996. It still works great, has been serviced regularly since it was installed and there is no good reason to replace it yet. However, it isn’t as energy efficient as more recent models.

The other aspect of this gas water heater is that it keeps the water hot 24/7, 365 days a year whether we need it or not. In my home, we generally only need hot water in the morning between 7 and 9 AM and in the evening, from 5 to 9 PM. On weekends, our schedule is a bit whacky and we need hot water from 8 AM to 9PM.

So, 30 hours for week days + 26 hours for weekends, that’s 56 hours / week where hot water is actually needed, as opposed to 168 hours / week when the heater is just left alone. In order words, in our house, we only really need a third of the water heater energy that we normally consume.

To make matters worse, the water heater was installed in the garage by the builder and it gets pretty cold during the winter time.

Considering that ~25% of our heating bill goes into heating water, I felt compelled to stop this senseless waste.

The idea that I came up with was to design a scheduler, configured to follow our weekly hot water usage pattern and capable of lowering the water heater temperature down to a minimum during off-hours.

I wanted it to be cheap to build with easy-to-find parts and very reliable as my wife does not appreciate cold showers: I decided to use a netduino-mini micro-controller, an AdaFruit DS1307 real-time clock and a servo to adjust the temperature of the water heater.

Here’s what the end result looks like in action:

Startup sequence and Manual override sequence

The slow moving speed of the servo is intentional in the application in order to minimize wear and tear on the servo’s gears and the overall assembly.

Application Overview
  • Every 60 seconds, the application checks the current date and time against a weekly schedule tracking daily timeframes when the gas water heater should be turned ON and when it should be turned OFF.
  • When it is time to turn the heater ON, the program first applies power the servo actuating the thermostat's dial and then tells the servo to turn the dial until it reaches the High Heat position. The servo power is then removed.
  • Conversely, when the time to turn the heater OFF comes, the same steps occur but this time, turning the dial all the way in the opposite direction.
  • The configuration of the schedule and the clock is done through a serial interface
  • The user can override the schedule by pressing a button which immediately turns the water heat ON. Pressing the button again resumes the normal schedule tracking process.

The application is built on the open source 'netduino.helpers' library which provides a set of hardware drivers written in C# targeting the netduino family of .Net Micro Framework micro-controllers.

The drivers used by this application are:

  • HS6635HBServo.cs: This class implements the driver for the HiTech HS-6635HB servo. While this class has only been tested with this particular servo, the implementation is generic enough and can be applied to many other brands of servos.
  • SerialUserInterface.cs: This class provides building blocks needed to display and receive data over a serial communication port. User input is handled in a interrupt-driven manner and results in a callback when the 'Enter' key is pressed. It also provides input value storage, basic input validation suitable for menu options and state management to track input sequences.
  • PushButton.cs: This class provides a wrapper around the InterruptPort class of the .Net Micro Framework and makes it easy to use momentary switch in an application without polling inputs.

Application Details

Walking through the application, there are some details worth noting:

The beginning of the application starts with a conditional compilation definition based on the hardware that you're targeting. Because the netduino boards have different format factors and on-board devices, pin assignments will vary.

By default, the application is targeting the mini: #define NETDUINO_MINI.

Any other definition would target the regular netduino. I was unable to test with the netduino Plus, so there's no provision for it in the code yet.

#define NETDUINO_MINIusing System;using System.Threading;using System.Collections;using Microsoft.SPOT;using Microsoft.SPOT.Hardware;using SecretLabs.NETMF.Hardware;using netduino.helpers.Hardware;using netduino.helpers.SerialUI;using netduino.helpers.Servo;using System.IO.Ports;

You must remember to include the proper reference to the Secret Labs assembly for the platform as well. If you don't your code might run, but expect very weird GPIO behavior

#if NETDUINO_MINI // You must ensure that you also have the reference set to SecretLabs.NETMF.Hardware.NetduinoMini in the project // You must also remove the SecretLabs.NETMF.Hardware.Netduino if it was there. using SecretLabs.NETMF.Hardware.NetduinoMini;#else // You must ensure that you also have the reference set to SecretLabs.NETMF.Hardware.Netduino in the project // You must also remove the SecretLabs.NETMF.Hardware.NetduinoMini if it was there. using SecretLabs.NETMF.Hardware.Netduino;#endif

Most of the core objects in the application last for the lifetime of the application, except for the SerialUserInterface object which may be recycled if a communication failure occurs.

namespace WaterHeaterController { public class Program {#if NETDUINO_MINI private static readonly OutputPort _servoPowerEnable = new OutputPort(Pins.GPIO_PIN_16, false); private static readonly PushButton _pushButton = new PushButton(Pin: Pins.GPIO_PIN_17, Target: PushButtonHandler); private static readonly OutputPort _ledOverride = new OutputPort(Pins.GPIO_PIN_13, false); private static readonly OutputPort _ledServoPowerEnable = new OutputPort(Pins.GPIO_PIN_15, false); private static readonly PWM _ledLowHeat = new PWM(Pins.GPIO_PIN_18); private static readonly PWM _ledHighHeat = new PWM(Pins.GPIO_PIN_19); private static readonly HS6635HBServo _servo = new HS6635HBServo(Pins.GPIO_PIN_20,minPulse: 700, centerPulse: 1600); private static SerialUserInterface _serialUI = new SerialUserInterface(Serial.COM2);#else private static readonly OutputPort _servoPowerEnable = new OutputPort(Pins.GPIO_PIN_D3, false); private static readonly PushButton _pushButton = new PushButton(Pin: Pins.ONBOARD_SW1, Target: PushButtonHandler); private static readonly OutputPort _ledOverride = new OutputPort(Pins.GPIO_PIN_D2, false); private static readonly OutputPort _ledServoPowerEnable = new OutputPort(Pins.GPIO_PIN_D4, false); private static readonly PWM _ledLowHeat = new PWM(Pins.GPIO_PIN_D5); private static readonly PWM _ledHighHeat = new PWM(Pins.GPIO_PIN_D6); private static readonly HS6635HBServo _servo = new HS6635HBServo(Pins.GPIO_PIN_D9,minPulse: 700, centerPulse: 1600); private static SerialUserInterface _serialUI = new SerialUserInterface();#endif private static readonly Schedule _schedule = new Schedule(); private static readonly Status _status = new Status(); private static readonly DS1307 _clock = new DS1307();

The following constants reference absolute servo positions expressed in degrees. Depending on the physical configuration of the servo installation (vertical, horizontal, left or right of the thermostat), you may need to change these values so that the servo moves in the correct direction.

The values below correspond to an 'upside down' servo (arm pointing downwards) placed on the left of the gas valve.

private const uint LowHeat = 180;private const uint Center = 100;private const uint HighHeat = 0;

When the application starts, it attempts to detect if the real-time clock was ever configured properly.

If an exception occurs, caused by a faulty initialization of the DateTime object normally returned by _clock.Get(), a default date time is assigned to the clock and the schedule held in the user-backed memory of the clock is filled with zeros and the internal oscillator of the clock is finally started.

private static void InitializeClock() { try { _clock.Get(); } catch (Exception e) { Debug.Print("Initializating the clock with default values due to: " + e); byte[] ram = new byte[DS1307.DS1307_RAM_SIZE]; _clock.Set(new DateTime(2011, 1, 1, 12, 0, 0)); _clock.Halt(false); _clock.SetRAM(ram); } }

The next step initializes the position of the servo: in an attempt to avoid sudden or harsh movements of the thermostat dial, the application expects the user to have positioned the arm of the servo manually at a 90 degree angle before powering ON the controller: with the servo arm's position centered, the call to _servo.Center() will hardly result in any motion at all.

InitializeClock(); Log("\r\nWater Heater Controller v1.0\r\n"); Log("Initializing..."); LoadSchedule(); PowerServo(true); Log("Centering servo"); _servo.Center(); Log("Setting heater on high heat by default"); _servo.Move(Center, currentHeat); Log("Running..."); PowerServo(false);

It's important to note that all calls to move the servo are prefixed with a PowerServo() call: the controller board was designed to control the 5 volt power supply to the servo through a transistor which gets activated / deactivated through that call. At the same time, an LED is turned ON / OFF indicating when the servo is moving.

To ensure that the motion of the servo is always nice and slow, preventing wear and tear on the gas valve as much as possible, _servo.Move() operates the servo with one degree increments at a time, with a short pause in between steps:

// Slowly moves the servo from a position to another public void Move(uint startDegree, uint endDegree, int delay = 80) { if (delay

This website is not affiliated, owned, or endorsed by Microsoft Corporation. It is a member of the Microsoft Partner Program.