Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Computer Controlled Watering System with C# or VB.NET

4.94/5 (93 votes)
30 Nov 2018CPOL14 min read 1   10.6K  
Setup a simple and cheap computer controlled watering system using VB.NET or C# and a parallel port relay controller

Screenshot - ezSprinkleScreen.png

Requirements

*Note, instructions for operating the ezSprinkle software interface are in the readme.txt included in both the binary and the source downloads at the top of this article.

In order to run the binaries, you must install the .NET Framework 2.0.

Also note, this project is available in both VB.NET and C# in the source downloads above.

Introduction

This is a fun project for building your own PC controlled watering system using a cheap parallel relay controller, a screwdriver, off the shelf solenoid valves, a power supply for the valves, some basic level wiring and soldering which can be completed by anyone, and of course, some clever VB.NET/C# :).

Now, if the use of the words PC, relay, solenoid valve, power supply, wiring, soldering, screwdriver, and VB.NET/C# all in the one sentence doesn't get the geek home handyman/handywoman salivating, I don't know what will!

I have this system watering my yard and I couldn't be happier, it is arguably just as reliable as a dedicated watering controller and far more convenient. I don't plan on ever putting my old dedicated watering controller to work again.

Out of the box, the ezSprinkle software project provides the following functionality:

  • Auto timer with fortnightly programming for setting alternating week programs (with selectable days).
  • Four individual run times for the selected days.
  • Any number of 'areas' can be programmed, though my device has a physical limit of eight areas.
  • Area 'Smart Copy' - By starting off defining the first area that runs for a fortnight, you can 'smart copy' that area to a new area, and the new area will copy all the days and times, but will add the duration for each defined time. The effect is an easy to configure back-to-back watering program. This will save many people a lot of time.
  • Time overlap detection - ezSprinkle will still allow you to overlap area times, but an indicator warns you of the overlap. Overlaps are sometimes not practical due to water pressure limitations as well as limitations on the solenoid valve power supply. It is not limited by the software.
  • Manual timer - Great for testing that your wiring works! You can also use this to give your plants a good water if you feel they are under-watered before you adjust the timing.
  • Continuous port signaling - I found that relays were mysteriously switching on, even without my program running. This was solved by using a timer to continuously communicate with the port. Any unauthorised relays that switch on are immediately switched off.
  • Rain sensor detection and logging - Rain sensor detection will automatically halt any watering and start watering again when the rain sensor is no longer activated. You can view the log at any time to show the activation and deactivation times of the rain sensor. If you really, really don't want a rain sensor, you can simply bridge pins 13 and 25, or modify the software to not look for 'always on' for the input.
  • 'Live' interface - You can change any part of the configuration on the fly and the controller will react in real time.

Background

I had already discovered the kitsrus K74A controller, and found a local supplier, ozitronics. I was actually a bit surprised at just how good the service was by Frank at ozitronics. Being a complete novice at electronics and relay wiring, Frank gave me a full explanation on how I could wire everything up, then sent me a diagram in a PDF file (see link above) to illustrate the finer points. Great service, considering I was a first-time, potentially one-off customer.

In a random search for port control code, I came across Levent Saltuklaroglu's article on controlling LED's (link expired, sorry).

That excellent article fired up a few neurons, and I figured this was the basis I could use to build my watering system controller.

Further along in development, I figured the LED sample didn't tell me anything about getting an input reading for my rain sensor, so it wasn't long before I found phebejtsov's article on reading from the parallel port using inpout32 - the same DLL as was used in the LED example. That article is here: Reading from Parallel Port using Inpout32.dll.

If you review those articles, they will give a great insight in reading/writing to the parallel port and why you need an external DLL to control the parallel port from .NET.

Here's a photo of the rain sensor attached to my shed to illustrate part of my implementation:

Screenshot - rainsensor.jpg

At first, the task seemed a bit daunting - I had to learn what controlling ports was all about, I had to design a scheduling type system with data persistence, dwell over how a user could most easily setup a watering program, and importantly, because this code is connected to the real world, I had to meticulously review all the what-if's and try to avoid any potential disasters by lots of validation and testing within the limitations of the hardware itself.

Wiring up the K74

For a basic wiring diagram, download K74-SIMP.zip, it is dead simple to follow. Here is how I wired my K74 up:

Screenshot - K74V2-Wired.jpg

I ran the wiring through the ceiling and down through the wall to a wall plate, then up to my K74.

Here are two of my solenoids ready for action:

Screenshot - solenoids.jpg

Using the Code

The code is complete and ready to start watering your yard for you. Both a Visual Basic .NET 2005 solution and a C# solution (VS2005) are included with all the source code for this project. I've included a lot of commenting in the code so it should be easy to understand what each method does.

Port numbers are used from the previously mentioned port control articles, but if you run into difficulty, you might want to look more carefully at phebejtsov's article and Levent Saltuklaroglu's article (link expired) which will explain more about controlling the parallel port from a fundamental perspective.

For the UI, because I am using a lot of repeating controls for different watering programs, I implemented cucgachthe's FindControl function to do the legwork for me. Given a control name, FindControl returns a control object which can then be manipulated. My repeating control names are all numbered sequentially in the naming, so FindControl makes it easy to iterate through all the controls to access them.

The data itself is stored in a dataset and serialized in/out of ezSprinkle on startup and exit, respectively. The user can also press the Save button to prevent loss of data in case the form is not gracefully closed. The data structure is flat to make things easily expandable. The system is horribly inefficient, but given the limitation of eight solenoids with this design, efficiency is not an issue.

I implemented a master timer in conjunction with a master port value to control the show. The master timer looks to see if we are running in auto, and if so, it calls code to iterate all the programmed times for all the areas to see what should be running. To handle overlapped times, the master time values are combined using 'Or' instead of '+' so that we can lazily keep slapping values into the master time without it accidentally iterating.

The actual method is:

VB.NET
Private Sub CloseAutoRelays(ByVal auto_area As Integer)
    Dim x As Integer

    ' iterate the relay selections for the selected area

    For x = 1 To 8
        If CBool(bindingSource1.List(auto_area).Row("Relay" & x)) Then
            mastervalue = (mastervalue Or CShort(Math.Pow(2, x - 1)))
        End If
    Next x
End Sub
C#
private void CloseAutoRelays(int auto_area)
{
    int x;
    // iterate the relay selections for the selected area

    for (x = 1; x <= 8; x++) {
        System.Data.DataRowView drv = 
           (System.Data.DataRowView)bindingSource1.List[auto_area];
        if ((bool)drv.Row["Relay" + x])
        {
            mastervalue = (short)(mastervalue | (short)Math.Pow(2, x - 1));
        }
    }
}

Navigation is largely handled by a bindingsource; all databound controls are connected to the bindingsource, not the dataset. Just for fun, I added a combobox at the top of the form that can also assist with navigation.

The combobox was setup in a very basic fashion; it works ;).

VB.NET
Sub CboAreaSelectedIndexChanged(ByVal sender As Object, _
    ByVal e As EventArgs) Handles cboArea.SelectedIndexChanged
    If Not navFlag Then
        navFlag = True
        bindingSource1.Position = bindingSource1.Find("AreaNum", _
        cboArea.SelectedValue)
    End If
    
    navFlag = False
End Sub
C#
public void CboAreaSelectedIndexChanged(object sender, EventArgs e)
{
    if (!navFlag)
    {
        navFlag = true;
        bindingSource1.Position = 
          bindingSource1.Find("AreaNum", cboArea.SelectedValue);
    }
    navFlag = false;
}

Points of Interest

One point of confusion that I found regarding the numeric up/down control was a well documented bug which prevents validation (and synchronization with the dataset) from typing into the text part of the control rather than using the up/down controls. I found this in all kinds of derived and enhanced controls. The simple solution to solve problems with the control was to handle the TextChanged event. As simple as this sounds, handling TextChanged is the answer. This event is not available as an event in the Properties box, nor is it offered in intellisense, but if coded in, it works. The caveat with this is that the automatic validations and data synchronization only occur when you access the value member of the control, so my check for time overlaps in the method CheckTimeClashUD actually triggers validation. In your own projects, you could simply read the numeric up/down control value into a dummy variable to initiate validation. If you set the value of the up/down control to its text, you can also prevent the text from being erased or overtyped with non-numeric characters. Here is my simple validation:

VB.NET
Private Sub validatenudManual(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles nudManual.TextChanged
    nudManual.Text = nudManual.Value
End Sub
C#
private void validatenudManual(System.Object sender, System.EventArgs e)
{
    nudManual.Text = nudManual.Value.ToString();
}

Another point of interest is my failsafe to lessen the probability of sticky relays causing over-watering. In this design, relay 8 is connected to the common line of the power outputs to the solenoid valves. Basically, no relay can activate a solenoid unless relay 8 is activated at the same time. Therefore, if a relay connected to a solenoid valve sticks, relay 8 will disconnect power to it. Conversely, if relay 8 sticks, the affected relay will cut its own power. It is unlikely that both relay 8 and another relay will start sticking at exactly the same time. It is up to you to replace any sticky relay so that later down the track, you don't end up with the situation where both the failsafe relay and the solenoid relay are sticking at the same time. This reduces your output lines by one, but it is worth the loss.

For the time overlap detection, I found it was one of those curly things that is not as obvious as it first seems. If you find yourself needing to test two timesets for overlap, you will find the following method quite helpful:

VB.NET
Private Function TimesOverlap(ByVal dt1a As Date, ByVal dt2a As Date, _
        ByVal dt1b As Date, ByVal dt2b As Date) As Boolean
    ' take a minute off end dates so as not to confuse the issue -
    ' technically the end time and start time of any back-to-back
    ' programs will fall within the same second

    dt2a = DateAdd(DateInterval.Minute, -1, dt2a)
    dt2b = DateAdd(DateInterval.Minute, -1, dt2b)
    If (dt1a <= dt1b And dt2a >= dt1b) Or (dt1a >= dt1b _
        And dt1a <= dt2b) Then
        Return True
    End If
    Return False
End Function
C#
private bool TimesOverlap(System.DateTime dt1a, System.DateTime dt2a, 
        System.DateTime dt1b, System.DateTime dt2b)
{
    // take a minute off end dates so as not to confuse the issue -
    // technically the end time and start time of any back-to-back
    // programs will fall within the same second

    dt2a = DateAndTime.DateAdd(DateInterval.Minute, -1, dt2a);
    dt2b = DateAndTime.DateAdd(DateInterval.Minute, -1, dt2b);
    if ((dt1a <= dt1b && dt2a >= dt1b) || (dt1a >= dt1b && dt1a <= dt2b))
    {
        return true;
    }
    return false;
}

The method takes the input values of timeset1 start time, timeset1 end time, timeset2 start time, and timeset2 end time. It will return true if there is an overlap.

In ezSprinkle, a time that overlaps with another will show a warning, indicating which time it overlaps with, as in the following screenshot:

Screenshot - timesingleoverlap.png

If your time overlaps with multiple other times, a plus sign will display after the warning as follows:

Screenshot - timemultioverlap.png

When you change the times so they don't overlap, the warning indicators will be removed. In the case of multiple overlaps, all overlaps will need to be removed before the warning indicator is removed. When you get down to the last overlap, the plus sign will be removed automatically.

Yet another bug (or undesirabe behaviour-by-design) is in the handling of a change to a checkbox when looking at its databound value. When I implemented CheckedChanged, I found that the datasource was still looking at the old value. I tried all kinds of things including implementing EndEdit, but this made the checkbox controls act erratically.

Finally, I found Jerry Nixon's blog where he described a similar experience. By utilising CheckStateChanged instead of CheckedChanged, the databinding takes place before I start looking at the row values, and because there's no forced EndEdit's or similar required, the controls behave nicely.

Conclusion

The K74 was the right choice for this project. Although I have since discovered that the K108 is probably better for a number of reasons, it is more expensive and doesn't really give any significant advantages to justify the cost difference for this project. The K108 is suited to a project where your PC is going to control more than just your watering system (or perhaps a more sophisticated implementation of a watering system) - it is suited for more detailed input monitoring, and also removes an annoying quirk of Windows XP (see the next paragraph) where XP will activate the relays during boot. I'll implement the K108 when I start my automatic driveway gate project :D.

When you boot up (assuming you're using Windows XP), XP in its wisdom sends a signal to all the K74 pins causing all the relays to close - and stay closed. My workaround is to have ezSprinkle load on startup, whereby the master relay value sets all the relays to an expected value (either automatically watering or all relays open). To get this to work, you need to install and run TweakUI and configure autologon - by setting the password, your PC will bypass the startup screen and logon normally. This probably won't work on a domain, but will be OK for home systems.

So, having done this, if your PC decides to reboot itself while you're not at home, you won't come home to find all the relays stuck closed and your watering system on. Conversely, because ezSprinkle will start up the way you left it, you won't come home from a holiday to find your plants dehydrated (provided your PC doesn't crash). My PC's bios has an option to automatically start the PC after a power failure, so you should investigate if you can set your PC up to do the same. This will prevent the system staying off after power returns after a blackout. Otherwise, I believe UPS systems can be configured to boot your PC if the power fails; the UPS battery runs down, then the power comes back online.

The only thing my old controller did that my setup does not is generate an error when there is no load detected in the solenoid valves. This in itself is not a showstopper, and in any case requires observation to determine the fault (i.e., a break in the line preventing one of the sprinkler areas coming on).

For a setup bordering on paranoia, you would want a flow meter located after the solenoid valve so that you could detect the expected water flow. Failing feedback from the flow meter, you would cut the power to the valve. This would prevent a line burst at or around the solenoid from spraying all over the ground whilst a program is running. Furthermore, moisture sensors could be implemented to only water when dry. Obviously, you cannot detect a break in the pipe after the solenoids unless you implement further flow sensors which would require a fair bit more setting up.

Incidentally, I did have one of my outlets burst without the system even being on. The failure occurred due to pressure always being at the outlet connections. Thankfully, it was on a Sunday, and I was at home to be able to quickly cut the water. The lesson here is to probably use either high quality polly connectors, or use brass - personally, I'm going to start upgrading to brass. The failure occurred in a joiner:

Screenshot - failure.jpg

In thinking about the software design, I considered the possibility of drawing in information from a local weather datasource to provide real-time data for watering, etc., but decided that the weather data may be accurate for the location of the weather station, but that data might not reflect what is happening at home. For example, often it is raining on one half of the town but not the other.

The other factor I considered was seasonal change. What I figured was that when a season changes here, the weather doesn't really change for weeks. Although plant growth may change, dry-out times stay the same, and change at some random point into the season. For this reason, I left ezSprinkle to be manually changed for any changing watering requirements. For those who know the watering requirements of plants through different times of the year, the serialized XML can easily be saved into seasons and the appropriate file be made current by copying it to the live data with the name of ezSprinkle.xml.

Finally, this is what we want to see at the end of the day, our PC watering our yard for us:

Screenshot - popups.jpg

:)

History

The current version of ezSprinkle works for a single bank of eight relays. I hope to update this little project to change over to the serial controller with the RF add-ons to remove the need to feed a wiring loom back to the PC.

  • 27th October, 2007: Added a C# conversion of the solution
  • 10th November, 2007: Fixed a number of problems:
    • Added a TextChanged handler to the NumericUpDown manual program duration control to ensure it is always validated - previously changing the duration manually would not enable/disable the manual start button.
    • Added code to check for overlapped times more thoroughly. Previously, overlaps did not consider day selections, so the overlap warning was incorrectly displaying when setting the same watering times on alternating days.
    • Added a CheckStateChanged handler to day selections so that they would also trigger overlap detection.
  • 10th November, 2007: Edit #2, fixed a bug with my binary compare variable for looking for overlaps. Variable wasn't resetting in the loop.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)