Sprinkler system, hours - number to datetime

I’ve built a garden sprinkler system with Wemos and relay, attached to a solenoid, using MQTT to/from OpenHAB to control. It works well, however I’m using a fairly basic number format for the “Next run hour”. Instead, I want the “Next run hour” to appear in a friendly format rather than just a number.

As per screenshot below, you can see the “Next run hour” is just a Number in 24 hour format, not that user friendly. I want to convert to, for example, “Next run hour = 5:00PM” I’ve read the datetime (joda) vs datetimetype (java) - and the screeds of confusion between them, yet have had a heap of log errors on type conversions etc, while trying to work out how to do using a DateTime item.

(Note: I haven’t done the “Check Weather” portion yet, that will simply check weather binding if its raining, and not run if it is)

The items file as follows (the 2nd “Next run hour” is what I want to move too)

/* Sprinkler System */
Number          sprinklerState                  "Sprinkler"		    			     <settings>                               { mqtt="<[mqttbroker:openhab/sprinkler/status:state:default]" }
String          sprinklerIPAddress              "Sprinkler IP [%s]"                  <network>                                { mqtt="<[mqttbroker:openhab/sprinkler/ipaddress:state:default]" }
Number   		sprinklerNextHour				"Next run hour [%.0f]"               <faucet>
DateTime   		sprinklerNextHourFriendly		"Next run hour [%1$tl:%1$tM %1$tp]"  <faucet>
Switch          sprinklerSwitch                 "Enable schedule"                    <lawnmower>
Switch			sprinklerIncludeWeather			"Check weather"						 <sun_clouds>
Switch          sprinklerRunNow                 "Run now / running"                  <rain>             (gSprinkler)          { mqtt=">[mqttbroker:openhab/sprinkler/trigger:command:ON:START], >[mqttbroker:openhab/sprinkler/trigger:command:OFF:STOP], <[mqttbroker:openhab/sprinkler/trigger:state:ON:START], <[mqttbroker:openhab/sprinkler/trigger:state:OFF:STOP]" }
Number          sprinklerRuntime                "Runtime remaining [%.0f mins]"      <time>                                   { mqtt="<[mqttbroker:openhab/sprinkler/runtime:state:default]" }
Number          sprinklerRunEvery               "Run every (hours) [%.0f]"           <flow>
Number          sprinklerDuration               "Run duration (min) [%.0f]"          <time>                                   { mqtt=">[mqttbroker:openhab/sprinkler/duration:command:*:${command}]" }

Relevant sections in the rules file:

rule "Calculate Uptime and Sprinkler status every 1 minute"
when 
	Time cron "0 * * * * ?" 
then 
    // ...does other stuff here...

	// Check sprinkler next run, if enabled...
	if (sprinklerSwitch.state == ON && boolSprinklerRunning == false)
	{
            // To check weather if enabled...

		// Check next hour to run
		var hourNow = now.getHourOfDay()
		var hourNext = sprinklerNextHour.state as Number

		// If hours match, time to run sprinkler
		if (hourNow == hourNext)
		{
			// Increment and store next run time 'hour'
			val RunEvery = sprinklerRunEvery.state as Number
			val RunNext = now.getHourOfDay + RunEvery

			if (RunNext > 23)
			{
				RunNext = RunNext - 24
			}
			sendCommand(sprinklerNextHour, RunNext)

			// Start sprinkler
			boolSprinklerRunning = true
			publish("mqttbroker", "openhab/sprinkler/duration", sprinklerDuration.state.toString)
			publish("mqttbroker", "openhab/sprinkler/trigger", "START")

			logInfo("sprinkler", "Sprinkler started. Next start hour " + sprinklerNextHour.state.toString)
		}
	}

end


rule "Sprinkler runtime complete"
when 
	Item sprinklerRuntime changed
then	
	if (sprinklerRuntime.state == NULL || sprinklerRuntime.state == 0)
	{
		logInfo("Sprinkler", "Sprinkler run cycle complete or cancelled")
		boolSprinklerRunning = false
		sendCommand(sprinklerRunNow, OFF)
	}
end


rule "Sprinkler run every hours changed"
when
	Item sprinklerRunEvery changed
then
	val RunEvery = sprinklerRunEvery.state as Number
	val RunNext = now.getHourOfDay + RunEvery
	if (RunNext > 23)
	{
		RunNext = RunNext - 24
	}

	sendCommand(sprinklerNextHour, RunNext)
end

So, should I persist with using only a DateTime item for the RunNext hour, or should I use a String item instead and format it myself, or should I use sprinklerNextHour simply as a hidden proxy item and utilise the other item to format the user friendly date time view?

What would be the easiest method here? Any suggestions appreciated.

Cheers

1 Like

I would use now.plus**

RunNext = now.plusHours(...)
//or
RunNext = now.plusSeconds(...)

http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTime.html

1 Like

Thanks, but that is exactly what I originally tried to do, but it didn’t work and I got the following error.

2018-02-05 13:18:31.514 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Sprinkler run every hours changed': An error occurred during the script execution: The name '<XFeatureCallImplCustom>.plusHours(<XFeatureCallImplCustom>)' cannot be resolved to an item or type.

I suspected it was because RunNext was not declared as DateTime, but if I do that I get:

2018-02-05 13:23:04.469 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Sprinkler run every hours changed': An error occurred during the script execution: Could not invoke method: org.joda.time.DateTime.plusHours(int) on instance: 13

Even if I .intValue the RunEvery, it still doesn’t work.

show your code

val RunEvery = sprinklerRunEvery.state as Number
val RunNext = now.plusHours(RunEvery.intValue)

And related error…

2018-02-05 13:31:36.757 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Sprinkler run every hours changed': An error occurred during the script execution: Could not invoke method: org.joda.time.DateTime.plusHours(int) on instance: 2018-02-05T13:31:36.755+13:00

EDIT: Hmmm, must have done something wrong originally because I think it just fixed incrementing the hour when using:

val RunEvery = sprinklerRunEvery.state as Number
val DateTime RunNext = now.plusHours(RunEvery.intValue)

Now I just need to workout how to display this as a DateTime item.

So the above is working on the Number item. Trying to get the DateTime working but the (last “sendCommand”) fails, tried doing this based on Rich’s type conversions page:

rule "Sprinkler run every hours changed"
when
	Item sprinklerRunEvery changed
then
	val RunEvery = sprinklerRunEvery.state as Number
	val DateTime RunNext = now.plusHours(RunEvery.intValue)

	logInfo("sprinkler", "Updating number item")
	sendCommand(sprinklerNextHour, RunNext.getHourOfDay as Number)

	logInfo("sprinkler", "Updating user friendly datetime item")
	sendCommand(sprinklerNextHourFriendly, DateTimeType.valueOf(RunNext))

end

2018-02-05 13:49:47.812 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Sprinkler run every hours changed': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.core.library.types.DateTimeType.valueOf(java.lang.String) on instance: null

ughh… this fixed it!

sendCommand(sprinklerNextHourFriendly, DateTimeType.valueOf(RunNext.toString))

:slight_smile:

Thanks for pointing me back in the right direction luckymallari!

For completeness, here is the working items and rules

/* Sprinkler System */
Number          sprinklerState                  "Sprinkler [MAP(status.map):%s]"		    			     <settings>                               { mqtt="<[mqttbroker:openhab/sprinkler/status:state:default]" }
String          sprinklerIPAddress              "Sprinkler IP [%s]"                  <network>                                { mqtt="<[mqttbroker:openhab/sprinkler/ipaddress:state:default]" }
DateTime   		sprinklerNextHour         		"Next run hour [%1$tl %1$tp]"        <faucet>
Switch          sprinklerSwitch                 "Enable schedule"                    <lawnmower>
Switch			sprinklerIncludeWeather			"Check weather"						 <sun_clouds>
Switch          sprinklerRunNow                 "Run now / running"                  <rain>             (gSprinkler)          { mqtt=">[mqttbroker:openhab/sprinkler/trigger:command:ON:START], >[mqttbroker:openhab/sprinkler/trigger:command:OFF:STOP], <[mqttbroker:openhab/sprinkler/trigger:state:ON:START], <[mqttbroker:openhab/sprinkler/trigger:state:OFF:STOP]" }
Number          sprinklerRuntime                "Runtime remaining [%.0f mins]"      <time>                                   { mqtt="<[mqttbroker:openhab/sprinkler/runtime:state:default]" }
Number          sprinklerRunEvery               "Run every (hours) [%.0f]"           <flow>
Number          sprinklerDuration               "Run duration (min) [%.0f]"          <time>                                   { mqtt=">[mqttbroker:openhab/sprinkler/duration:command:*:${command}]" }

Rules…

val Boolean boolSprinklerRunning = false

rule "Calculate Uptime and Sprinkler status every 1 minute"
when 
	Time cron "0 * * * * ?" 
then 
    // ...does other stuff here...

	// Check sprinkler next run, if enabled...
	if (sprinklerSwitch.state == ON && boolSprinklerRunning == false)
	{
		// Check next hour to run
		val hourNow = now.getHourOfDay()
		val getNextHour = new DateTime((sprinklerNextHour.state as DateTimeType).calendar.timeInMillis)
		val hourNext = getNextHour.getHourOfDay()

		// If hours match, time to run sprinkler
		if (hourNow == hourNext)
		{
			// Increment and store next run 'hour'
			val RunEvery = sprinklerRunEvery.state as Number
			val RunNext = now.plusHours(RunEvery.intValue)
			sendCommand(sprinklerNextHour, DateTimeType.valueOf(RunNext.toString))

			// Start sprinkler
			boolSprinklerRunning = true
			publish("mqttbroker", "openhab/sprinkler/duration", sprinklerDuration.state.toString)
			publish("mqttbroker", "openhab/sprinkler/trigger", "START")

			logInfo("sprinkler", "Sprinkler started. Next start hour " + sprinklerNextHour.state.toString)
		}
	}

end


rule "Sprinkler runtime complete"
when 
	Item sprinklerRuntime changed
then	
	if (sprinklerRuntime.state == NULL || sprinklerRuntime.state == 0)
	{
		logInfo("Sprinkler", "Sprinkler run cycle complete or cancelled")
		boolSprinklerRunning = false
		sendCommand(sprinklerRunNow, OFF)
	}
end


rule "Sprinkler run every hours changed"
when
	Item sprinklerRunEvery changed
then
	val RunEvery = sprinklerRunEvery.state as Number
	val DateTime RunNext = now.plusHours(RunEvery.intValue)
	sendCommand(sprinklerNextHour, DateTimeType.valueOf(RunNext.toString))
end

Now the GUI appears much more user friendly!

Any suggestion for improvement please let me know…

1 Like

Thanks for taking the time to post your full working code, wish more people that ask for help do this in their threads. I will be doing a similar thing with the D1 mini or a nodeMCU so will find this post helpful at some point. If doing this I would try and make your design be pin compatible with the opensprinkler project.

No worries - and I agree. I generally learn from other examples, once I read it and run through the logic it often makes more sense.

Below is full Arduino code if you’re interested…

// Turn garden sprinkler on or off
//
// Last update: 04/02/2018
// Author: chimera
//

#include <PubSubClient.h>
#include <ESP8266WiFi.h>

// Wemos data 
const char* OPENHABMYIPADDRESS = "openhab/sprinkler/ipaddress";   // IP address of Wemos (send to OpenHAB)
const char* OPENHABSTATUS = "openhab/sprinkler/status";           // Wemos Alive status (send to OpenHAB)

// Publish
const char* OPENHABRUNTIME = "openhab/sprinkler/runtime";         // 0 = not running, > 1 = minutes left to run (send to OpenHAB)

// Subscribe
const char* OPENHABTRIGGER = "openhab/sprinkler/trigger";         // Trigger ON / OFF (receive from OpenHAB)
const char* OPENHABDURATION = "openhab/sprinkler/duration";       // Runtime duration mins (receive from OpenHAB)

// Wifi
const char* ssid     = "your ssid here";
const char* password = "your password here";

// MQTT Broker - change to your broker IP
IPAddress MQTT_SERVER(172, 16, 223, 254);   

// Define message buffer and publish string
char mqtt_topic[20];
char message_buff[16];

// Send alive messages every 30 seconds
int iAlive;

// Sprinkler boolean
bool bSprinklerEnable = false;
bool bSprinklerRunning = false;

// Sprinkler counters
long iDurationMinutes = 0;  // Minutes we need to run for
long lDurationCounter = 0;  // Milliseconds we've currently run for

// Maximum runtime of 30 minutes - ensure it never exceeds this regardless of what we're sent
const int MAXIMUMRUNTIME = 30; 

// Wemos pins
int RELAYPIN = D1;

// Wifi Client
WiFiClient wifiClient;

// Callback to Arduino from MQTT (inbound message arrives for a subscription)
void callback(char* topic, byte* payload, unsigned int length) 
{

  // Messaging inbound from MQTT broker
  int iChar = 0;
  for(iChar=0; iChar<length; iChar++) {
    message_buff[iChar] = payload[iChar];
  }
  message_buff[iChar] = '\0';

  // Convert buffer to string
  String msgString = String(message_buff);
  Serial.println("Inbound: " + String(topic) + ":" + msgString);

  // Flash pin
  digitalWrite(LED_BUILTIN, LOW);
  delay(10);
  digitalWrite(LED_BUILTIN, HIGH);

  if (strcmp(topic, OPENHABSTATE) == 0)
  {
      // Perform MQTT callback request
      if (msgString == "ON")
      {
        bSprinklerEnable = true;
      }
      else if (msgString == "OFF")
      {
        bSprinklerEnable = false;
      }
  }
  else if (strcmp(topic, OPENHABTRIGGER) == 0)
  {
      // Perform MQTT callback request
      if (msgString == "RESET")
      {
        Serial.println("Rebooting ESP!");
        ESP.reset();
      }
      else if (msgString == "START")
      {
        Serial.println("Request to start sprinkler");
        StartStopSprinkler(true);
      }
      else if (msgString == "STOP")  
      {
        Serial.println("Request to stop sprinkler");
        StartStopSprinkler(false);
      }
      else
      {
        Serial.print("Unknown Message: ");
        Serial.println(msgString);
      }
  }
  else if (strcmp(topic, OPENHABDURATION) == 0)
  {
    iDurationMinutes = msgString.toInt();
    Serial.println("Runtime duration updated to " + String(iDurationMinutes) + " mins");
  }
}

// Define Publish / Subscribe client (must be defined after callback function above if in use)
PubSubClient mqttClient(MQTT_SERVER, 1883, callback, wifiClient);


//
// Setup the ESP for operation
//
void setup()
{

  // Set builtin LED as connection indicator
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  // Define GPIO pin as relay output
  pinMode(RELAYPIN, OUTPUT);
  digitalWrite(RELAYPIN, LOW);

  // Sprinkler running or not
  bSprinklerRunning = false;
  
  // Debug USB Serial
  Serial.begin(115200);
  Serial.println(" ");

}


void loop()
{

  // If not MQTT connected, try connecting
  if (!mqttClient.connected())  
  {

    // Check its not cause WiFi dropped
    startWIFI();
    
    // If not MQTT connected, blink LED 5 times in succession
    Serial.println("Connecting to MQTT Broker");

    int iRetries;

    // WiFi must be connected, try connecting to MQTT 
    while (mqttClient.connect("sprinkler", OPENHABSTATUS, 0, 1, "0") != 1) 
    {

      Serial.println("Error connecting to MQTT (State:" + String(mqttClient.state()) + ")");
      for (int iPos=1; iPos <= 10; iPos++)
      {
        // Flash pin
        digitalWrite(LED_BUILTIN, LOW);
        delay(75);
        digitalWrite(LED_BUILTIN, HIGH);
        Serial.print("."); 
      }
      
      // Double check WiFi state or # retries while in this loop just incase we timed it badly!
      if (WiFi.status() != WL_CONNECTED || iRetries > 5) 
      { 
        Serial.println("No MQTT connection...");
        break; 
      }

      // Make sure we're not stuck here forever, loop and reconnect WiFi if needed
      iRetries++;

    }

    // Tell MQTT we're alive
    PublishMQTTMessage(OPENHABSTATUS, "1");
    iAlive=0;

    // Subscribe to trigger (for rebooting or status)
    Serial.print("Subscribing to ");
    Serial.println(OPENHABTRIGGER);
    mqttClient.subscribe(OPENHABTRIGGER);
    
    Serial.print("Subscribing to ");
    Serial.println(OPENHABDURATION);
    mqttClient.subscribe(OPENHABDURATION);
    
    // Reset counters, default is sprinkler stopped
    StartStopSprinkler(false);

  }

  // If sprinkler is running, check runtime and when to turn it off
  if (bSprinklerRunning == true)
  {
    // Increment run counter in seconds
    lDurationCounter++;

    // Send update every 1 minute
    if ((lDurationCounter % 60) == 0)
    {
      Serial.println("Runtime remaining: " + String(iDurationMinutes - (lDurationCounter / 60)) + " minutes");
      PublishMQTTMessage(OPENHABRUNTIME, String(iDurationMinutes - (lDurationCounter / 60)));
    }
    
    // Have we reached the runtime minutes or maximum runtime? 
    if (lDurationCounter >= (iDurationMinutes * 60) || lDurationCounter >= (MAXIMUMRUNTIME * 60))
    {
      Serial.println("Timer up or maximum runtime reached, stopping sprinkler");
      StartStopSprinkler(false);
    }
  }

  // Delay 1000ms (1 second) as a counter
  delay(1000);
  iAlive++;
  
  // Send I'm alive message every 30 seconds
  if (iAlive >= 30)
  {
    Serial.println("Telling OpenHAB we're still alive");
    PublishMQTTMessage(OPENHABSTATUS, "1");
    PublishMQTTMessage(OPENHABMYIPADDRESS, WiFi.localIP().toString());
    iAlive=0;
  }

  // Do it all over again
  mqttClient.loop();
  
}


//
// Start or stop the sprinkler via relay
//
void StartStopSprinkler(bool bStopOrStart)
{

  switch (bStopOrStart)
  {
    case false:
    {
      if (bSprinklerRunning == true)
      {
        lDurationCounter=0;
        digitalWrite(RELAYPIN, LOW);
        bSprinklerRunning=false;
        PublishMQTTMessage(OPENHABRUNTIME, "0");
        Serial.println("Sprinkler system stopped");
      }
      break;
    }

    case true:
    {
       // Only ever allow if sprinkler system is enabled
       if (bSprinklerEnable == true)
       {
         // Don't run unless we have a minute duration to work off
         if (iDurationMinutes == 0) // Shouldn't ever get in here
         {
            Serial.println("Runtime 0 minutes, needs updating");
            PublishMQTTMessage(OPENHABRUNTIME, "0");
         }
         else if (iDurationMinutes > 0)
         {
            if (bSprinklerRunning == false)
            {
              Serial.println("Duration interval set for " + String(iDurationMinutes) + " minutes");
              digitalWrite(RELAYPIN, HIGH);
              bSprinklerRunning=true;
              PublishMQTTMessage(OPENHABRUNTIME, String(iDurationMinutes));
            }
         }
       }
       break;
    }
  }

  
}


//
// Starts WIFI connection
//
void startWIFI() 
{

    // If we are not connected
    if (WiFi.status() != WL_CONNECTED) 
    {
      int iTries;
      iTries=0;
      digitalWrite(LED_BUILTIN, LOW);
      Serial.println("Starting WIFI connection");
      WiFi.mode(WIFI_STA);
      WiFi.disconnect(); 
      WiFi.begin(ssid, password);
    
      // If not WiFi connected, retry every 2 seconds for 15 minutes
      while (WiFi.status() != WL_CONNECTED) 
      {
        iTries++;
        Serial.print(".");
        delay(2000);
        
        // If can't get to Wifi for 15 minutes, reboot ESP
        if (iTries > 450)
        {
           Serial.println("TOO MANY WIFI ATTEMPTS, REBOOTING!");
           ESP.reset();
        }
      }

      Serial.println("");
      Serial.println("WiFi connected");
      Serial.println(WiFi.localIP());
      digitalWrite(LED_BUILTIN, HIGH);

      // Let network have a chance to start up
      delay(1500);

    }

}

// Publish MQTT data to MQTT broker
void PublishMQTTMessage(String sMQTTSub, String sMQTTData)
{

  // Quick check we're connected (otherwise crash!)
  if (mqttClient.connected())
  {
    // Define and send message about zone state
    sMQTTData.trim();
    sMQTTSub.trim();

    // Convert to char arrays
    sMQTTSub.toCharArray(mqtt_topic, sMQTTSub.length()+1);
    sMQTTData.toCharArray(message_buff, sMQTTData.length()+1);

    // Publish MQTT message
    mqttClient.publish(mqtt_topic, message_buff);
    
    // Visual debug
    digitalWrite(LED_BUILTIN, LOW);
    delay(150);
    digitalWrite(LED_BUILTIN, HIGH);
  }

}
2 Likes

Some further information for anyone’s info…

Excluding plumbing and fittings to run pipe out to the garden plus a couple of taps that I bought locally, this setup cost me under US$13…

Wemos D1 mini = US$2.95
Wemos relay shield = US$0.75
12V solenoid valve = US$3.07
20pcs Garden Irrigation = US$6.01
Circuit board, voltage regulator and capacitors already had, but probably US$0.50 worth

I had a 12V wall wart lying around already.

Some setup images…

Solenoid with tap prior, incase solenoid dies and needs replacing…

Wemos and relay shield, not quite mounted properly yet :slight_smile: Solenoid requires 12V power to open (solenoid and wemos powered by same power supply - otherwise if powered separate and Wemos lost power while sprinkler was running, then water could stay on until fixed!)

Plumbing runs behind my retainer wall, to another garden tap then off to garden irrigation

About 8 x mini garden sprinklers hang off the irrigation

For control via Alexa and charting for seeing when the sprinklers have been running, I have also added the following items:

Switch          sprinklerProxyOnOff           "Sprinkler system"      ["Switchable"]
Number	     	sprinkler_Period		      "Chart Period"
Number   		Weather_Rain          			"Rain forecast [%.2f mm/h]"          <rain>  				                  { weather="locationId=home, type=precipitation, property=rain" }

Sitemap (I renamed ‘sprinklerNextHour’ to ‘sprinkerNextRuntime’ as it was more logical) and added in rain forecast at the bottom

			Frame label="Garden Sprinkler"
			{
				Switch item=sprinklerSwitch
				Switch item=sprinklerIncludeWeather
				Text item=sprinklerNextRuntime {
					Frame {
						Switch item=sprinkler_Period label="Period" mappings=[0="Day", 1="Week", 2="Month"]
						Image url="http://localhost:8080/rrdchart.png?items=sprinklerRunNow&period=D" visibility=[sprinkler_Period==0, sprinkler_Period==Uninitialized]
						Image url="http://localhost:8080/rrdchart.png?items=sprinklerRunNow&period=W" visibility=[sprinkler_Period==1]
						Image url="http://localhost:8080/rrdchart.png?items=sprinklerRunNow&period=M" visibility=[sprinkler_Period==2]
					}
				}
				Setpoint item=sprinklerRunEvery minValue=1 maxValue=24 step=1
				Setpoint item=sprinklerDuration minValue=1 maxValue=30 step=1
				Switch item=sprinklerRunNow
				Text item=sprinklerRuntime
				Text item=Weather_Rain
			}

Extra rules

rule "Sprinkler Proxy turned on / off, for Alexa control"
when
	Item sprinklerProxyOnOff changed
then
	// Proxy item changed, turn sprinkler on or off
	if (sprinklerProxyOnOff.state == ON)
	{
		logInfo("Sprinkler", "Request to turn sprinkler system ON")
		sendCommand(sprinklerRunNow, ON)
	}
	else
	{
		logInfo("Sprinkler", "Request to turn sprinkler system OFF")
		sendCommand(sprinklerRunNow, OFF)
	}
end


rule "Keep sprinkler on/off and proxy item in sync"
when
	Item sprinklerRunNow changed
then
	if (sprinklerRunNow.state != null)
	{
		if (sprinklerRunNow.state == ON)
		{
			sendCommand(sprinklerProxyOnOff, ON)
		}
		else
		{
			sendCommand(sprinklerProxyOnOff, OFF)
		}
	}
end

And the rule that runs every minute, I use the weather binding to check the forecast - using the irrigation controller rule from the sample rules page on this site. https://github.com/openhab/openhab1-addons/wiki/Samples-Rules#irrigation-controller

EG:

	// Check sprinkler next run, if enabled...
	if (sprinklerSwitch.state == ON && boolSprinklerRunning == false)
	{
		// Check next hour to run
		val hourNow = now.getHourOfDay()
		val getNextHour = new DateTime((sprinklerNextRuntime.state as DateTimeType).calendar.timeInMillis)
		val hourNext = getNextHour.getHourOfDay()

		// If hours match, time to run sprinkler
		if (hourNow == hourNext)
		{
			// Increment and store next run 'hour'
			val RunEvery = sprinklerRunEvery.state as Number
			val RunNext = now.plusHours(RunEvery.intValue)
			sendCommand(sprinklerNextRuntime, DateTimeType.valueOf(RunNext.toString))
			val RainThreshold = sprinkler_RainThreshold.state as Number

		    	// check for any rain in the last 24 hours
		        var rainInLast24Hours = Weather_Rain.maximumSince(now.minusHours(24), "rrd4j")
	
				// default to the current rain value in case there is nothing in our history
		    	var rain = Weather_Rain.state
    			if (rainInLast24Hours !== null) { rain = rainInLast24Hours.state }

		        // check if any rain is forecast
		        var rainToday = Weather_Id.state == "chanceflurries" ||
    				Weather_Id.state == "chancerain" ||
    				Weather_Id.state == "chancesleet" ||
    				Weather_Id.state == "chancesnow" ||
    				Weather_Id.state == "chancetstorms" ||
    				Weather_Id.state == "flurries" ||    				
    				Weather_Id.state == "rain" || 
    				Weather_Id.state == "sleet" ||
    				Weather_Id.state == "snow" ||
    				Weather_Id.state == "tstorms"

		        var rainTomorrow = Weather_Tomorrow.state == "chanceflurries" ||
    				   Weather_Tomorrow.state == "chancerain" ||
    				   Weather_Tomorrow.state == "chancesleet" ||
    				   Weather_Tomorrow.state == "chancesnow" ||
    				   Weather_Tomorrow.state == "chancetstorms" ||
    				   Weather_Tomorrow.state == "flurries" ||
    				   Weather_Tomorrow.state == "rain" || 
    				   Weather_Tomorrow.state == "sleet" ||
    				   Weather_Tomorrow.state == "snow" ||
      				   Weather_Tomorrow.state == "tstorms"


		    	// Shutoff irrigation if there has been rain or rain is forecast
		        var logMessage = ""
		    	if (rain > RainThreshold) 
			{
		    	    logMessage = "Rain in the last 24 hours (" + rain + " mm) is above our threshold (" + RainThreshold + " mm) - irrigation disabled!"
		    	} 
			else if (rainToday) 
			{
		    	    logMessage = "Rain is forecast for today - irrigation disabled!"
		    	} 
			else if (rainTomorrow) 
			{
		    	    logMessage = "Rain is forecast for tomorrow - irrigation disabled!"
		    	}

			// Check if we're also confirming weather is ok
			if (sprinklerIncludeWeather.state == ON && logMessage != "")
			{
				logInfo("sprinkler", logMessage)
			}
			else
			{
				// Start sprinkler
				boolSprinklerRunning = true
				publish("mqttbroker", "openhab/sprinkler/duration", sprinklerDuration.state.toString)
				publish("mqttbroker", "openhab/sprinkler/trigger", "START")

				logInfo("sprinkler", "Sprinkler next start hour " + sprinklerNextRuntime.state.toString)
			}
		}
	}
3 Likes

Hello, any updates regarding your irrigation project? have you made modifications to your code of items, sitemaps, as well as rules and persistence? Can you share it?
Thanks

Hi, mostly the same. Here is what I have for the rules (extras for Alexa):

rule "Calculate Uptime and Sprinkler status every 1 minute"
when 
	Time cron "0 * * * * ?" 
then 
	// Wait til we've booted up
	if (swSystemBootUp.state == OFF)
	{

		// If sprinkler system is enabled and sprinkler is not currently running...
		if (sprinklerSwitch.state == ON && boolSprinklerRunning == false)
		{
			// Check next hour to run
			var hourNow = now.getHourOfDay()
			var getNextHour = new DateTime((sprinklerNextRuntime.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli)
			var hourNext = getNextHour.getHourOfDay()

			// If hours match, time to run sprinkler
			if (hourNow == hourNext)
			{

				// Increment and store next run 'hour'
				var RunEvery = sprinklerRunEvery.state as Number
				var RunNext = now.plusHours(RunEvery.intValue)
				sendCommand(sprinklerNextRuntime, DateTimeType.valueOf(RunNext.toString))
				var RainThreshold = sprinkler_RainThreshold.state as Number

				// check for any rain in the last 24 hours
				var rainInLast24Hours = Weather_Rain.maximumSince(now.minusHours(24), "rrd4j")
	
				// default to the current rain value in case there is nothing in our history
				var rain = Weather_Rain.state
				if (rainInLast24Hours !== null) { rain = rainInLast24Hours.state }

				// check if any rain is forecast
				var rainToday = Weather_Id.state == "chanceflurries" ||
					Weather_Id.state == "chancerain" ||
					Weather_Id.state == "chancesleet" ||
					Weather_Id.state == "chancesnow" ||
					Weather_Id.state == "chancetstorms" ||
					Weather_Id.state == "flurries" ||    				
					Weather_Id.state == "rain" || 
					Weather_Id.state == "sleet" ||
					Weather_Id.state == "snow" ||
					Weather_Id.state == "tstorms"

				var rainTomorrow = Weather_Tomorrow.state == "chanceflurries" ||
					Weather_Tomorrow.state == "chancerain" ||
					Weather_Tomorrow.state == "chancesleet" ||
					Weather_Tomorrow.state == "chancesnow" ||
					Weather_Tomorrow.state == "chancetstorms" ||
					Weather_Tomorrow.state == "flurries" ||
					Weather_Tomorrow.state == "rain" || 
					Weather_Tomorrow.state == "sleet" ||
					Weather_Tomorrow.state == "snow" ||
					Weather_Tomorrow.state == "tstorms"
   	
			    	// Shutoff irrigation if there has been rain or rain is forecast
			        var logMessage = ""
			    	if (rain > RainThreshold) 
				{
			    	    logMessage = "Rain in the last 24 hours (" + rain + " mm) is above our threshold (" + RainThreshold + " mm) - irrigation disabled!"
			    	} 
				else if (rainToday) 
				{
			    	    logMessage = "Rain is forecast for today - irrigation disabled!"
			    	} 
				else if (rainTomorrow) 
				{
			    	    logMessage = "Rain is forecast for tomorrow - irrigation disabled!"
			    	}


				// If we're effectively just rescheduling, turn on dummy switch for 15 seconds
				if (logMessage != "")
				{
					sendCommand(sprinklerProxySwitch, ON)
					createTimer(now.plusSeconds(15), [|
						logInfo("sprinkler", "Proxy Switch OFF")
		        			sendCommand(sprinklerProxySwitch, OFF)
					])
				}

				// Check if we're also confirming weather is ok
				if (sprinklerIncludeWeather.state == ON && logMessage != "")
				{
					logInfo("sprinkler", logMessage)
				}
				else if (logMessage == "")
				{
					// Start sprinkler
					boolSprinklerRunning = true
					
					publish("mqttbroker", "openhab/sprinkler/duration", sprinklerDuration.state.toString)
					publish("mqttbroker", "openhab/sprinkler/trigger", "START")

					logInfo("sprinkler", "Sprinkler next start hour " + sprinklerNextRuntime.state.toString)
				}
			}
		}
	}
end



rule "Sprinkler runtime complete"
when 
	Item sprinklerRuntime changed
then	
	if (swSystemBootUp.state == OFF)
	{
		if (sprinklerRuntime.state == NULL || sprinklerRuntime.state == 0)
		{
			logInfo("Sprinkler", "Sprinkler run cycle complete or cancelled")
			boolSprinklerRunning = false
			sendCommand(sprinklerRunNow, OFF)
		}
	}
end



rule "Sprinkler run every hours changed"
when
	Item sprinklerRunEvery changed
then
	var RunEvery = sprinklerRunEvery.state as Number
	var DateTime RunNext = now.plusHours(RunEvery.intValue)
	sendCommand(sprinklerNextRuntime, DateTimeType.valueOf(RunNext.toString))
end



rule "Sprinkler Proxy turned on / off, for Alexa control"
when
	Item sprinklerProxyOnOff changed
then
	if (swSystemBootUp.state == OFF && sprinklerProxyOnOff.state !== null)
	{
		// Proxy item changed, turn sprinkler on or off
		if (sprinklerProxyOnOff.state == ON)
		{
			logInfo("Sprinkler", "Received request to turn sprinkler system ON")
			sendCommand(sprinklerRunNow, ON)
		}
		else
		{
			logInfo("Sprinkler", "Received request to turn sprinkler system OFF")
			sendCommand(sprinklerRunNow, OFF)
		}
	}
end



rule "Keep sprinkler on/off and proxy item in sync"
when
	Item sprinklerRunNow changed
then
	if (swSystemBootUp.state == OFF && sprinklerRunNow.state !== null)
	{
		if (sprinklerRunNow.state == ON)
		{
			sendCommand(sprinklerProxyOnOff, ON)
		}
		else
		{
			sendCommand(sprinklerProxyOnOff, OFF)
		}
	}
end

Items (“sprinkler system” is switchable because I use with Alexa to turn on via voice command)

/* Sprinkler System */
Group           gSprinkler    				                                                               (gHouse)
Number          sprinklerState                   "Sprinkler [MAP(status.map):%s]"	     <settings>        (gSprinkler)                               { mqtt="<[mqttbroker:openhab/sprinkler/status:state:default]" }
String          sprinklerIPAddress               "Sprinkler IP [%s]"                     <network>         (gSprinkler)                               { mqtt="<[mqttbroker:openhab/sprinkler/ipaddress:state:default]" }
DateTime   		sprinklerNextRuntime       		 "Next run time [%1$tl %1$tp]"           <faucet>          (gSprinkler)
Switch          sprinklerSwitch                  "Enable schedule"                       <lawnmower>       (gSprinkler)
Switch			sprinklerIncludeWeather			 "Check weather"						 <sun_clouds>      (gSprinkler)
Switch          sprinklerRunNow                  "Run now / running"                     <water>           (gSprinkler)                               { mqtt=">[mqttbroker:openhab/sprinkler/trigger:command:ON:START], >[mqttbroker:openhab/sprinkler/trigger:command:OFF:STOP], <[mqttbroker:openhab/sprinkler/trigger:state:ON:START], <[mqttbroker:openhab/sprinkler/trigger:state:OFF:STOP]" }
Switch          sprinklerProxySwitch             "Not run, rescheduled"                  <water>           (gSprinkler)
Number          sprinklerRuntime                 "Runtime remaining [%.0f mins]"         <time>                                                       { mqtt="<[mqttbroker:openhab/sprinkler/runtime:state:default]" }
Number          sprinklerRunEvery                "Run every (hours) [%.0f]"              <flow>            (gSprinkler)
Number          sprinklerDuration                "Run duration (min) [%.0f]"             <time>            (gSprinkler)                               { mqtt=">[mqttbroker:openhab/sprinkler/duration:command:*:${command}]" }
Switch          sprinklerProxyOnOff              "Sprinkler system"                                                           ["Switchable"]
Number 			sprinkler_Period				 "Chart Period"                                            (gSettings)
Number 			sprinkler_RainThreshold		     "Rain threshold"                        <sewerage>        (gSprinkler)
Number   		Weather_Rain          			 "Rain forecast [%.2f mm/h]"             <rain>            (gWeather)                                 { channel="openweathermap:weather-and-forecast:1cb13ebf:local:forecastHours06#rain" }

Sitemap

			Frame label="Garden Sprinkler"
			{
				Switch item=sprinklerSwitch
				Switch item=sprinklerIncludeWeather
				Text item=sprinklerNextRuntime {
					Frame {
						Switch item=sprinkler_Period label="Period" mappings=[0="Day", 1="Week", 2="Month"]
						Image url="http://localhost:8080/rrdchart.png?items=sprinklerRunNow,sprinklerProxySwitch&period=D" visibility=[sprinkler_Period==0, sprinkler_Period==Uninitialized]
						Image url="http://localhost:8080/rrdchart.png?items=sprinklerRunNow,sprinklerProxySwitch&period=W" visibility=[sprinkler_Period==1]
						Image url="http://localhost:8080/rrdchart.png?items=sprinklerRunNow,sprinklerProxySwitch&period=M" visibility=[sprinkler_Period==2]
					}
				}
				Setpoint item=sprinklerRunEvery minValue=1 maxValue=24 step=1
				Setpoint item=sprinklerDuration minValue=1 maxValue=30 step=1
				Switch item=sprinklerRunNow
				Text item=sprinklerRuntime
				Setpoint item=sprinkler_RainThreshold minValue=1 maxValue=20 step=1
				Text item=Weather_Rain
			}

Transform file, ‘sprinkler.map’

CLOSED=Idle
OPEN=Triggered
NULL=Unknown
-=Unknown

Arduino code is still the same.

1 Like