IoT: Hunter Garden Watering Hack

We have installed a Hunter watering system in our garden and after I realized how bloody the original controller is, I decided to create my own MQTT-enabled controller based on ESP8266 microprocessor with native WIFI connectivity.

The following controller allows integration of the watering system into your MQTT-based IoT system. A multitude of automation possibilities and intelligence opens up of course ;-)

Features

  • Supports 4 electromagnetic valves triggered each by relais
  • Supports dive pump in a cistern or direct connection to tap
  • Supports analog cistern sensor if cistern becomes full
  • Reports every status change to MQTT
  • Lets you control all valves and the pump via MQTT
  • Software update OTA via WIFI (in case you buried the controller somewhere)
  • Signals basic health using multi color LEDs

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "ESP8266HTTPClient.h"
#include "ESP8266httpUpdate.h"

/*
 * Data
 * ====
 * 
 * sensors/hunter started
 * sensors/hunter/health {online, offline}
 * sensors/hunter/rssi {int32}
 * sensors/hunter/capacity/value [0-1024]
 * sensors/hunter/capacity {full, normal}
 * sensors/hunter/state {JSON}
 * 
 * 
 * Commands
 * ========
 * 
 * sensors/hunter/pump {ON, OFF}
 * sensors/hunter/channel1 {ON, OFF}
 * sensors/hunter/channel2 {ON, OFF}
 * sensors/hunter/channel3 {ON, OFF}
 * sensors/hunter/channel4 {ON, OFF}
 * sensors/hunter/getstate
 * sensors/hunter/version
 * sensors/hunter/flash {"http://__REMOVED__/hunter.bin"}
 *
 * Written by Marco Maniscalco
 */

const char* FIRMWARE_VERSION = "1.1";

const char* ssid     = "__REMOVED__";
const char* password = "__REMOVED__";
const char* mqtt_server = "mqtt";

char clientRSSI[50];

void callback(char* topic, byte* payload, unsigned int length);
void sendState();
void switchAllOff();
void switchPump(int state);
void switchChannel1(int state);
void switchChannel2(int state);
void switchChannel3(int state);
void switchChannel4(int state);
void getState();
void switchChannel(int num, int state);

const int PIN_PUMP = 1;
const int PIN_CHANNEL1 = 14;
const int PIN_CHANNEL2 = 12;
const int PIN_CHANNEL3 = 16;
const int PIN_CHANNEL4 = 13;
const int PIN_LED_RED = 4;
const int PIN_LED_GREEN = 3;
const int PIN_LED_BLUE = 5;

#define MQTT_KEEPALIVE 10

volatile bool forcePumpOn = false;

WiFiClient espClient;
PubSubClient client(mqtt_server, 1883, callback, espClient);

void green() {
  digitalWrite(PIN_LED_GREEN, HIGH);  
  digitalWrite(PIN_LED_BLUE, LOW);
  digitalWrite(PIN_LED_RED, LOW);
}

void blue() {
  digitalWrite(PIN_LED_GREEN, LOW);  
  digitalWrite(PIN_LED_BLUE, HIGH);
  digitalWrite(PIN_LED_RED, LOW);
}

void red() {
  digitalWrite(PIN_LED_GREEN, LOW);  
  digitalWrite(PIN_LED_BLUE, LOW);
  digitalWrite(PIN_LED_RED, HIGH);
}

void callback(char* topic, byte* payload, unsigned int length) {

    char message_buff[100];
    for(int i=0; i<length; i++) {
      message_buff[i] = payload[i];
    }
    message_buff[length] = '\0';

    if(strcmp(topic, "sensors/hunter/flash") == 0) {
      ESPhttpUpdate.rebootOnUpdate(true);
      ESPhttpUpdate.update(message_buff);
    } 
    else if(strcmp(topic, "sensors/hunter/pump") == 0) {
      if(strcmp(message_buff, "ON") == 0) {
        switchPump(HIGH);        
        forcePumpOn = true;
      }
      else {
        switchPump(LOW);        
        forcePumpOn = false;
      }
    }
    else if(strcmp(topic, "sensors/hunter/channel1") == 0) {
      if(strcmp(message_buff, "ON") == 0)
        switchChannel(1, LOW);        
      else
        switchChannel(1, HIGH);        
    }
    else if(strcmp(topic, "sensors/hunter/channel2") == 0) {
      if(strcmp(message_buff, "ON") == 0)
        switchChannel(2, LOW);        
      else
        switchChannel(2, HIGH);        
    }
    else if(strcmp(topic, "sensors/hunter/channel3") == 0) {
      if(strcmp(message_buff, "ON") == 0)
        switchChannel(3, LOW);        
      else
        switchChannel(3, HIGH);        
    }
    else if(strcmp(topic, "sensors/hunter/channel4") == 0) {
      if(strcmp(message_buff, "ON") == 0)
        switchChannel(4, LOW);        
      else
        switchChannel(4, HIGH);        
    }
    else if(strcmp(topic, "sensors/hunter/all") == 0) {
      if(strcmp(message_buff, "OFF") == 0)
        switchAllOff();
    }
    else if(strcmp(topic, "sensors/hunter/getstate") == 0) {
      publishState(); 
    }
}

void switchChannel(int num, int state) {
  if(num == 1) {
    switchChannel1(state);
    switchChannel2(HIGH);
    switchChannel3(HIGH);
    switchChannel4(HIGH);
  }
  else if(num == 2) {
    switchChannel1(HIGH);
    switchChannel2(state);
    switchChannel3(HIGH);
    switchChannel4(HIGH);
  }
  else if(num == 3) {
    switchChannel1(HIGH);
    switchChannel2(HIGH);
    switchChannel3(state);
    switchChannel4(HIGH);
  }
  else if(num == 4) {
    switchChannel1(HIGH);
    switchChannel2(HIGH);
    switchChannel3(HIGH);
    switchChannel4(state);
  }
}

void switchAllOff() {
  switchPump(LOW);
  switchChannel1(HIGH);
  switchChannel2(HIGH);
  switchChannel3(HIGH);
  switchChannel4(HIGH);  
}

void switchPump(int state) {
  digitalWrite(PIN_PUMP, state);  
  publishState();
}

void switchChannel1(int state) {
  digitalWrite(PIN_CHANNEL1, state);  
  publishState();
}

void switchChannel2(int state) {
  digitalWrite(PIN_CHANNEL2, state);  
  publishState();
}

void switchChannel3(int state) {
  digitalWrite(PIN_CHANNEL3, state);  
  publishState();
}

void switchChannel4(int state) {
  digitalWrite(PIN_CHANNEL4, state);  
  publishState();
}

void reconnectWifi() {
    WiFi.begin(ssid, password); 
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
    }
    String str = String(WiFi.RSSI());
    str.toCharArray(clientRSSI, str.length()+1);
}

void reconnectMQTT() {
  
  while (!client.connected()) {
   
    if (client.connect("hunter","sensors/hunter/health", 0, true, "offline")) {
      client.setCallback(callback);
      
      client.publish("sensors/hunter/rssi", clientRSSI); delay(0);
      
      client.publish("sensors/hunter", "started"); delay(0);

      client.publish("sensors/hunter/health", "online", true); delay(0);

      client.publish("sensors/hunter/version", FIRMWARE_VERSION); delay(0);

      client.subscribe("sensors/hunter/#"); delay(0);

    } else {
      delay(1000);
    }
  }
}

void publishState() {

  int pumpState = digitalRead(PIN_PUMP);
  int channel1State = digitalRead(PIN_CHANNEL1);
  int channel2State = digitalRead(PIN_CHANNEL2);
  int channel3State = digitalRead(PIN_CHANNEL3);
  int channel4State = digitalRead(PIN_CHANNEL4);

  if(pumpState == LOW) pumpState = LOW; else pumpState = HIGH;
  if(channel1State == HIGH) channel1State = LOW; else channel1State = HIGH;
  if(channel2State == HIGH) channel2State = LOW; else channel2State = HIGH;
  if(channel3State == HIGH) channel3State = LOW; else channel3State = HIGH;
  if(channel4State == HIGH) channel4State = LOW; else channel4State = HIGH;

  char logString[130]; 
  sprintf(logString,"{\"pump\": %i,\"channel1\": %i, \"channel2\": %i, \"channel3\": %i, \"channel4\": %i}",pumpState, channel1State, channel2State, channel3State, channel4State);
  
  client.publish("sensors/hunter/state", logString, true);

  delay(0);
}

void setup() {
  Serial.begin(115200);

  pinMode(PIN_PUMP, OUTPUT);
  pinMode(PIN_LED_GREEN, OUTPUT);
  pinMode(PIN_LED_RED, OUTPUT);
  pinMode(PIN_LED_BLUE, OUTPUT);
  pinMode(PIN_CHANNEL1, OUTPUT);
  pinMode(PIN_CHANNEL2, OUTPUT);
  pinMode(PIN_CHANNEL3, OUTPUT);
  pinMode(PIN_CHANNEL4, OUTPUT);

  switchAllOff();
  
  red();
}

int lastTime = 0, lastState = 0;

void loop() {

  if(WiFi.status() != WL_CONNECTED) {
    reconnectWifi();
    blue();
  }
 
  if (!client.connected()) {
    reconnectMQTT();
    green();
    publishState();
  }

  int time = millis();

  if(time - lastTime > 60 * 1000) {
    lastTime = time;

    int sensorReading = analogRead(A0); // read the sensor on analog A0
    
    // notüberlauf aktivieren
    if(!forcePumpOn) { 
      if(sensorReading <500) {
        if(lastState != 1) {
          lastState = 1;
          client.publish("sensors/hunter/capacity", "full", true); delay(0);
          
          switchPump(HIGH);
          // notüberlauf-ventil öffnen
          switchChannel(4, LOW);
        }
      }
      else {
        if(lastState != 0) {

          lastState = 0;
          client.publish("sensors/hunter/capacity", "normal", true); delay(0);
  
          switchPump(LOW);    
          // notüberlauf-ventil schließen
          switchChannel(4, HIGH);
        }
      }
    }
  }
    
  client.loop();
  delay(0);
}