Off-The-Shelf Hacker: Adding MQTT and Cron to the Lawn Sprinkler Project

Off-The-Shelf Hacker: Adding MQTT and Cron to the Lawn Sprinkler Project

Off-The-Shelf Hacker: Adding MQTT and Cron to the Lawn Sprinkler Project

This week we’ll continue our journey on building an automated sprinkler system. The project highlights key design and implementation concepts that off-the-shelf hackers will face in the systems they build.

While we could just program on/off times for each individual sprinkler head, directly on a standalone Arduino, a networked approach presents several benefits. Networking permits interaction between devices so they can accomplish things together, perhaps across vast distances. Additionally, network-enabled Arduino clones, such as the NodeMCU boards are nearly as cheap as a plain old non-networked Arduino. Might as well use them. There are also tons of libraries and sample programs you can leverage for your initial bare-bones proof-of-concept sprint. No need to create all the code from scratch.

In our case, we’re using the NodeMCU/relay board as an edge device. It has a limited program and physical space. It is also inexpensive and there are only a few parts, making it pretty rugged for being out in the garage. My rig uses the NodeMCU as a relay controller and receives commands from a “smarter” Raspberry Pi, that will manage the sprinkler scheduling, analysis and interaction with other systems further up the line.

How do you make gadgets talk to each other? I’m using the MQTT messaging protocol. Let’s get a general overview of why and how to use the MQTT for sprinkler control. The code for the NodeMCU/relay board will appear later.

Why Use MQTT?

MQTT is a good communication model for networked physical computing and Internet of Things (IoT) projects because it is simple, reliable and lightweight. It is also mainstream and was designed for an industrial environment. I wrote about using MQTT on wearables back in January 2018. General installation instructions are in that article. Instead of the conference badge, I installed MQTT on Hedley the Skull for this project. Nowadays, the MQTT broker and client applications are a standard component for all my Linux devices.

The automated sprinkler system consists of two basic and several optional components. The MQTT broker resides on Hedley, this time as a matter of convenience. At some point, the broker will be moved to a dedicated Raspberry Pi server or up into the cloud. MQTT starts automatically whenever Hedley boots up. The MQTT client resides on the NodeMCU/relay board device and is embedded as part of the Arduino code.

We can also have an optional client on another Linux machine, such as my ASUS notebook. I installed the entire MQTT package, which includes both the client and broker parts. I used the notebook for testing.

The control idea for the sprinkler system is to send data, using MQTT to the NodeMCU and turn a relay on or off. Once the broker is running on Hedley, we can simply send data to a topic, from the notebook, with the following command line.

drtorq-laptop%  mosquitto_pub -h 192.168.1.107 -t inTopic -m 1

This particular combination turns on relay #1. The 192.168.1.107 is the local network address of the MQTT broker (on Hedley).

Once functionality is confirmed with manually sending data through the broker to the NodeMCU/relay we can use cron to automate the process.

Trigger the Relays with cron

Cron is a native Linux program for scheduling automated program execution. Traditionally Unix/Linux system management duties, like running nightly data backup processes are managed with cron. Set up a simple text file with your desired program execution time and when that time comes up, the program will run. You can automate just about any task using cron.

For testing, I entered the start and stop times for a couple of sprinkler heads and used the mosquitto_pub command indexing the corresponding NodeMCU/relay with the -m (message) option. Sending a “1” turns on the #1 relay. The “8” key turns on the #8 relay and so on. Sending a “0” turns off ALL the relays. While we could turn on more than one relay at a time, the NodeMCU’s little voltage regulator will only be able to supply enough juice for a couple of relays before overheating. Being aware of power requirements and how they affect the hardware components, is something to keep in mind when designing your gadgets.

For the proof-of-concept, I used cron on my ASUS Linux notebook. You could just as easily set up the cron table on Hedley the Skull, the steampunk conference badge or any other Linux machine on your network. Remember we are running the MQTT broker on Hedley. You can even use the MQTT client on the same machine as the broker if desired. Just open a terminal and type in a mosquitto_pub/mosquitto_sub command. As long as the devices are powered up and connected to the network, everything will work fine.

Save yourself some heartache and use the traditional “crontab -e” command to edit your cron table.

doc-laptop%  crontab -e

Make edits to the table, then use a “Ctrl-o” and Ctrl-x” to exit. The cron daemon will automatically restart after you save the file and exit crontab. Your file(s) will never run, without restarting the daemon. Sad to say it took me a long time to figure that last bit out, back in my Unix system admin days. At the time I didn’t have a little crontab -e script.

Here’s my cron table on the Linux notebook.

SHELL=/bin/bash
MAILTO=doc@drtorq.com
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
#
# Each line contains the date/time followed by the command and any options
# note: the user-name defaults to your login user.
#
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed
#
# run the mosquitto_pub commands
# 
30 07 * * * mosquitto_pub -h 192.168.1.107 -t inTopic -m 8
30 09 * * * mosquitto_pub -h 192.168.1.107 -t inTopic -m 0
45 09 * * * mosquitto_pub -h 192.168.1.107 -t inTopic -m 2
30 10 * * * mosquitto_pub -h 192.168.1.107 -t inTopic -m 0

The NodeMCU Code

The code for our NodeMCU device is pretty straightforward. I uploaded it to the NodeMCU board, from my Linux notebook using version 1.8.7 of the Arduino IDE.

/*
 Basic ESP8266 MQTT example
*/

#include 
#include 

// Update these with values suitable for your network.

const char* ssid = "my-local-ap";
const char* password = "falala-lala";
const char* mqtt_server = "192.168.1.107";

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
void setup_wifi() {

  delay(10);
  // Connect to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {
    digitalWrite(16, LOW);  
  } 
  else if((char)payload[0] == '2') {
    digitalWrite(5, LOW);
  } 
  else if((char)payload[0] == '3') {
    digitalWrite(4, LOW); 
  } 
  else if((char)payload[0] == '4') {
    digitalWrite(0, LOW);
  } 
  else if((char)payload[0] == '5') {
    digitalWrite(2, LOW); 
  }
  else if((char)payload[0] == '6') {
    digitalWrite(14, LOW); 
  }
  else if((char)payload[0] == '7') {
    digitalWrite(12, LOW); 
  }
  else if((char)payload[0] == '8') {
    digitalWrite(13, LOW); 
  }
  else {
    digitalWrite(16, HIGH);
    digitalWrite(5, HIGH);
    digitalWrite(4, HIGH);
    digitalWrite(0, HIGH);
    digitalWrite(2, HIGH);
    digitalWrite(14, HIGH);
    digitalWrite(12, HIGH);
    digitalWrite(13, HIGH);
  }

}
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void setup() {
  pinMode(16, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  pinMode(5, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(0, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(14, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
  
  digitalWrite(16, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(4, HIGH);
  digitalWrite(0, HIGH);
  digitalWrite(2, HIGH);
  digitalWrite(14, HIGH);
  digitalWrite(12, HIGH);
  digitalWrite(13, HIGH);
  
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 50, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
  }
}

We go through the usual initialization, including the networking and MQTT libraries.

The callback section is where all the code/physical action takes place, grabbing messages from the MQTT broker and switching the relays on/off as needed. Notice that I turn all the relays OFF whenever we get a “0.” This helps restrict having only one relay on at a time.

We also turn all the relays OFF during the setup phase. I noticed that whenever the NodeMCU powered up it would turn all the relays ON by default. Just add a little code and the problem was eliminated.

Some serial print statements are visible throughout the code. These could be removed once everything is stable and happy.

What’s Next

In keeping with industry practice, security is an afterthought for this proof-of-concept. Obviously, if this were going to be a commercial system, that topic would need considerable attention. For now, the setup will reside behind my network firewall. If I come home one day and find all the sprinklers running at once, I may have a bigger problem.

It might make sense to add in some default behaviors, like turning off ALL the relays after some set period of time, regardless of the data coming in from the MQTT broker. This would be similar to a “watchdog timer” that resets a processor if there is a major error in a running program. While delivery of MQTT messages are very reliable, we still have to account for missing a message. We certainly don’t want a sprinkler (or all of them) running continuously for 72 hours if the NodeMCU doesn’t get the message or there is an error. Cutting power to the board, relays and solenoids might be a way to go. That’s known as a “dead man’s switch.”

Another idea is to gather some data from the yard, like reading a rain gauge and send it up our physical computing stack chain for analysis. That functionality is easily added to the NodeMCU/relay Arduino code, along with a few sensors and analysis programs up on the Raspberry Pi.

Feature image via Pixabay.

 

Leave a Reply

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