Setting the On and Off Times

We want to turn the water valves on and off at different times of the day. We’ll need some way to record those values. Because we have three valves, we might use an array, with one entry for each valve. This will also make it easier if we later want to add more water valves. You might recall that we used an array named buffer to store the characters as they were sent from the Processing sketch. Arrays are described briefly in “Variables”.

Here’s one way to do that:

const int NUMBEROFVALVES = 3;
const int NUMBEROFTIMES = 2;

int onOffTimes [NUMBEROFVALVES][NUMBEROFTIMES];
NOTE

For simplicity, we’ll assume you turn the water on and off at the same time every day of the week. As your programming skills increase, you can modify this to accommodate different schedules on different days of the week, and even multiple times in one day. When you start a project, it’s good to start with the most simple system possible, and add features as you verify that things work properly.

Note that instead of using fixed numbers for the dimensions of the array, I first created two constant variables. This serves to remind me what these numbers mean, and make it easier to change later. Constant variable names are in uppercase letters to remind us that they are constants.

If you’ve never seen a two-dimensional array, don’t be frightened. Think of it as a spreadsheet. The number in the first [] is the number of rows, and the number in the second [] is the number of columns. A row represents a valve, and we’ll use the first column to store the time at which to turn the valve on, and the second column to store the time at which to turn the valve off.

Let’s make constant variables for the column numbers as well. Remember that the index of elements within an array always starts at zero:

const int ONTIME = 0;
const int OFFTIME = 1;

Next we need a way to input this information; that is, some sort of user interface. Typically, a user interface is a menu, but we’re going to make something extremely simple using the serial monitor.

We needed a way to tell Arduino what colour to make the light? As I mentioned there, because Arduino is a simple device, we chose a simple way to codify the colour.

We’re going to do a similar thing here: codify the times in as simple a way as possible.

We need to be able to set the ON time and OFF time for each valve. We could use a number indicating the desired valve followed by the letter N for “on” and F for “off,” followed by the time. We could input the time in 24-hour notation, e.g., 0135 for 1:35 a.m. Thus, we would type

2N1345 2F1415

to turn valve 2 on at 1:45 p.m. and off at 2:15 p.m.

To make our life easier, let’s insist that we always use uppercase letters for N and F.

In our code, we would need to parse, or separate, the string that we type into the correct groups.

NOTE

A group of consecutive characters is called a string.

If you look at the Arduino sketch (Example 6-2), you’ll see we made use of Serial.available() and Serial.read(), which are functions of the serial object. It turns out the serial object has more functions, as described at Arduino.

We’ll use the Serial.parseInt() function, which reads digit characters and converts them to integers. It stops when it sees a character that isn’t a number. We read the letters (N or F) directly with Serial.read().

For the purposes of testing, we’ll just print out the entire array after each line is read, as shown in Example 8-1.

Example 8-1. Parsing the commands sent to the irrigation system
/*
 Example 8-1. Parsing the commands sent to the irrigation system
 */
const int NUMBEROFVALVES = 3;
const int NUMBEROFTIMES = 2;
int onOffTimes [NUMBEROFVALVES][NUMBEROFTIMES];

const int ONTIME = 0;
const int OFFTIME = 1;

void setup(){
  Serial.begin(9600);
};

void loop() {
  // Read a string of the form "2N1345" and separate it
  // into the first digit, the letter, and the second number

  // read only if there is something to read
  while (Serial.available() > 0) {

    // The first integer should be the valve number
    int valveNumber = Serial.parseInt();

    // the next character should be either N or F
    // do it again:
    char onOff = Serial.read();

    // next should come the time
    int desiredTime = Serial.parseInt();
      //Serial.print("time = ");
      //Serial.println(desiredTime);

    // finally expect a newline which is the end of
    // the sentence:
    if (Serial.read() == '\N') {
      if ( onOff == 'N') { // it's an ON time
        onOffTimes[valveNumber][ONTIME] = desiredTime;
      }
      else if ( onOff == 'F') { // it's an OFF time
        onOffTimes[valveNumber][OFFTIME] = desiredTime;
      }
      else { // something's wrong
        Serial.println ("You must use upper case N or F only");
      }
    } // end of sentence
    else {
      // Sanity check
      Serial.println("no Newline character found"); 
    }

    // now print the entire array so we can see if it works
    for (int valve = 0; valve < NUMBEROFVALVES; valve++) {
      Serial.print("valve # ");
      Serial.print(valve);
      Serial.print(" will turn ON at ");
      Serial.print(onOffTimes[valve][ONTIME]);
      Serial.print(" and will turn OFF at ");
      Serial.print(onOffTimes[valve][OFFTIME]);
      Serial.println();
    }
  } // end of Serial.available()
}

After loading the sketch into Arduino, open the serial monitor and check the baud rate and line-ending selection boxes in the lower-right corner of the serial monitor. Select Newline and 9600 baud. The line-ending type makes sure that every time you end a line by pressing the Enter key on your computer, your computer will send a newline character to your Arduino.

For instance, if you want to turn on valve #1 at 1:30 p.m., type 1N1330 and press Enter. You should see this:

valve # 0 will turn ON at 0 and will turn OFF at 0
valve # 1 will turn ON at 1330 and will turn OFF at 0
valve # 2 will turn ON at 0 and will turn OFF at 0

In the sketch, notice that I check to make sure that the character between the numbers is either N or F, and that after the second number is a newline character. This sort of “sanity checking” is handy to catch mistakes you might make while typing, which might confuse your program. These sort of checks are also useful for catching mistakes in your program. You might think of other sanity checks; for instance, you might check that the time is valid, e.g., it is less than 2359, and that the valve number is less than NUMBEROFVALVES.

NOTE

A program that is designed to handle only the correct data is very delicate, as it is sensitive to any errors, whether caused by user input or a mistake elsewhere. By checking the data before operating on it, your program will be able to identify errors instead of trying to operate with faulty data, which could lead to unexpected or erroneous behavior. This makes your program robust, which is a highly desireable quality, especially since humans won’t always do what they are supposed to do.

Before we go on, I want to show you a new trick. This chunk of code we just developed is lengthy, and we still have a lot to add. It’s going to start getting confusing to read and manage the program.

Fortunately, we can make use of a very clever and common programming technique. In “Pass Me the Parmesan” we explained what a function is, and that setup() and loop() are two functions that Arduino expects to exist. So far you’ve created your entire program within these two functions.

What I didn’t emphasize is that you can create other functions the same way you create setup() and loop().

Why is this important? Because it’s a very convenient way to break a long and complicated program into small functions, each of which has a specific task. Furthermore, since you can name those functions anything you want, if you use names that describe what they do, reading the program becomes much simpler.

NOTE

Any time a chunk of code that does a specific task becomes large, it is a good candidate for becoming a function. How large is large enough? That’s up to you. My rule of thumb is that as soon as a chunk of code takes up more than two screens, it is a candidate for becoming a function. I can keep two screenfuls in my head, but not more than that.

An important consideration is whether the chunk of code can easily be extracted. Does it depend on many variables that are visible only within another function? As you learn more about variable scope, you’ll see this is important too.

For instance, the code we just developed reads a command from the serial monitor, parses it, and then stores the times into the array. If we made this a function called expectValveSetting(), our loop is simply:

void loop() {

    expectValveSettings();

}

That’s much easier to read, and most important, easier to understand as we develop the rest of the program.

Of course we need to create this function, which we do like this:

void expectValveSettings() {
  // Read a string of the form "2N1345" and separate it
  // into the first digit, the letter, and the second number

  // read only if there is something to read
  while (Serial.available() > 0) {
        // ... rest of the code not repeated here
         }
        

The rest is exactly the same as Example 8-1; I left out the rest of the function because I didn’t want to waste another two pages.

Now we can turn to the other things we need to do, and make them functions as well.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *