Complete watering system based on openHAB

Below is a full watering algorithm, I hope it will be useful for people who think about garden watering .

It was based on https://github.com/openhab/openhab1-addons/wiki/Samples-Rules#irrigation-controller.
In my solution, I control three zones and I base on rain sensor and weather forecast.
All configuration is performed using openHAB mobile app.
In few days I will add photos of watering installations.

Hardware:

  • 3 water valves - one per watering section, controlled using MCP23017 and relays. I use Hunter valves.
  • rain sensor - OPEN when wet, CLOSED when dry. I use Hunter rain sensor.
  • Raspberry with Openhabian 1.3 (OpenHab 2.1)

Prerequisite installation

  • MCP23017 binding - I use MCP23017 I/O expander for controlling valves/sensors but you can use for example GPIO
  • Pushover binding - it is optional binding if you want to send messages to your phone
  • InfluxDB persistence - if you want store event history
  • mabdb persistence - for state recovery

Item file

String EventHist  // event history, used with Influxdb persistence

Switch WtrValveFront  "Watering - front section"      <water>   { mcp23017="{ address:20, pin:'A0', mode:'DIGITAL_OUTPUT', defaultState:'HIGH'}" } 
Switch WtrValveBack   "Watering - back section"       <water>   { mcp23017="{ address:20, pin:'A1', mode:'DIGITAL_OUTPUT', defaultState:'HIGH'}" }
Switch WtrValveLine   "Watering - watring line"       <water>   { mcp23017="{ address:20, pin:'A2', mode:'DIGITAL_OUTPUT', defaultState:'HIGH'}" }
Switch WtrLED         "Watering LED"                            { mcp23017="{ address:20, pin:'B7', mode:'DIGITAL_OUTPUT', defaultState:'LOW'}" }

// Rain sensor - OPEN=wet, CLOSED=dry
Contact  WtrRainSensor "Rain sensor [MAP(RainSensor_PL.map):%s]"       <rain>    { mcp23017="{ address:21, pin:'B7', mode:'DIGITAL_INPUT'}" }
Contact  WtrStartBtn   "Manual watering start"                  	   <water>   { mcp23017="{ address:21, pin:'B5', mode:'DIGITAL_INPUT'}" } 
Contact  WtrAuto       "Watering automation [MAP(WtrAuto_PL.map):%s]"  <switch>  { mcp23017="{ address:21, pin:'A4', mode:'DIGITAL_INPUT'}" }

// Watering parameter
String	WtrStartTime     		"Watering hour"	                 				<calendar> 
Number	WtrDurationFront 		"Watering duration - front section[%d min]"     <pressure> 
Number	WtrDurationBack  		"Watering duration - back section[%d min]"      <pressure>  
Number	WtrDurationLine  		"Watering duration - watering line[%d min]"     <pressure>  
Number	WtrScaleFactor			"Correction coefficient [%d %%]"  		        <pressure>	
Number  WtrRainfallForecast     "Rainfall forecast [%.1f mm]"   			 <humidity> 
String  WtrRainfallForecastDate "Date of forecast [%s]"   			         <humidity>  
String  WtrLastDate     "Last watering date [%s]"   			         <humidity>  
String  Notification_Proxy_Wtr   // START - start watering , STOP - stop watering

Rules

/*
 * Watering rules
 */
import org.openhab.core.library.types.*
import org.joda.time.*
import java.util.Date

var int maxNoWtrDays = 2	        // acceptable maximum days without watering 
var DateTime lastWtrDate = null     // date of last watering or rain 
val minimumReqRainfall = 10         // minimum required rainfall that stops watering procedure
var rainfallMm = 0.0			    // rainfall forecast for next 24h 
var missingRainfall = 0.0           // difference between rainfall forecast-minimum req.rainfall 
var isWatering = false 			    // is watering now
 
// watering timers
var Timer WtrValveFrontStartTimer = null
var Timer WtrValveFrontStopTimer  = null
var Timer WtrValveBackStartTimer  = null
var Timer WtrValveBackStopTimer   = null
var Timer WtrValveLineStartTimer  = null
var Timer WtrValveLineStopTimer   = null 
var Timer WateringStopTimer       = null		// timer used to execute action after full watering cycle

// Constants for controling contact with the opposite logic
var OnOffType  ON_R 
var OnOffType  OFF_R

// === Data required to get wheather forecast from OpenWeatherMap ===
val APPID  = "<YOUR KEY>"		// your key from OpenWeatherMAp
val cityId = <ID OF CITY> 	    // city id from OpenWeatherMAp
val cnt    = 8                  // number of 3h segments, 8 segments = 24h
val openwheatherUrl = "http://api.openweathermap.org/data/2.5/"
// =============================================================================

// For debug
val checkUserStartTime = true  // check watering time ?
val checkWtrRainSensor = true  // check rain sensor ?

rule "Watering_garden_startup"
	when
		System started		
	then
		// pseudoconstant initialization
		ON_R = OFF
		OFF_R = ON
    	isWatering = false
    	
    	// valve initialization
    	WtrValveFront.sendCommand(OFF_R)
    	WtrValveBack.sendCommand(OFF_R)
    	WtrValveLine.sendCommand(OFF_R)
		
		// displaying watering parameters, waiting 80s to be sure that mapdb restore all values
		createTimer(now.plusSeconds(80)) [| 
			logInfo( "FILE", "Watering_garden_startup: ================ Wtr param  initialization ================")
			logInfo( "FILE", "Watering_garden_startup: WtrStartTime       [" + WtrStartTime.state + "]")
			logInfo( "FILE", "Watering_garden_startup: WtrDurationFront   [" + WtrDurationFront.state + "]")
			logInfo( "FILE", "Watering_garden_startup: WtrDurationBack    [" + WtrDurationBack.state + "]")
			logInfo( "FILE", "Watering_garden_startup: WtrDurationLine    [" + WtrDurationLine.state + "]")
			logInfo( "FILE", "Watering_garden_startup: WtrScaleFactor     [" + WtrScaleFactor.state + "]")
			logInfo( "FILE", "Watering_garden_startup: WtrAuto            [" + WtrAuto.state + "]")
			logInfo( "FILE", "Watering_garden_startup: WtrRainSensor      [" + WtrRainSensor.state + "]")
		   	logInfo( "FILE", "Watering_garden_startup: checkWtrRainSensor [" + checkWtrRainSensor + "]")
    		logInfo( "FILE", "Watering_garden_startup: checkUserStartTime [" + checkUserStartTime + "]")
		]
end 

// Rule executed when rain sensor detect rain - stop watering
rule "Rain_sensor_rain_activated"
    when
	    Item RainSensor changed from CLOSED to OPEN or
        Item RainSensor changed from NULL to OPEN
    then
    	logInfo( "FILE", "Rain_sensor_rain_detected: Rain detected, stopping watering if running")
    	EventHist.sendCommand("Rain sensor is wet")
    	Notification_Proxy_Wtr.sendCommand("STOP")
end

rule "Rain_sensor_rain_deactivated"
    when
	    Item RainSensor changed from OPEN to CLOSE or
        Item RainSensor changed from NULL to CLOSE
    then
   		logInfo( "FILE", "Rain_sensor_rain_detected: Rain detected is dry")
    	EventHist.sendCommand("Rainsensor is dry")
end
    
// User set watering to "automatic watering"  
rule "Wtr_auto_ON" 
	when
		Item WtrAuto changed from OPEN to CLOSED or
        Item WtrAuto changed from NULL to CLOSED
	then
		EventHist.sendCommand("Watering mode is set to automatic")
end
	
// ustawienie podlewania na manualne 
rule "Wtr_auto_OFF" 
	when
		Item WtrAuto changed from CLOSED to OPEN or
        Item WtrAuto changed from NULL to OPEN
	then
		EventHist.sendCommand("Watering mode is set to manual")
end

// Manual start of watering  
rule "Watering_manual_start/stop"
    when
	    Item WtrStartBtn changed from OPEN to CLOSED or
        Item WtrStartBtn changed from NULL to CLOSED
    then
		// if watering in progress this rule stops watering 
    	if ( isWatering == false ) {
    		logInfo( "FILE", "Watering_manual_start/stop: Start watering, manual activation")
    		EventHist.sendCommand("User pressed button START watering")
    		Notification_Proxy_Wtr.sendCommand("START")
    	} else { 
    		logInfo( "FILE", "Watering_manual_start/stop: Stoping  watering, manual activation")
    		EventHist.sendCommand("User pressed button STOP watering")
    		Notification_Proxy_Wtr.sendCommand("STOP")
    	}    
end

// This rule stops / starts watering 
rule "Watering_starting/stoping"
	when 
		Item Notification_Proxy_Wtr received update
	then
		var String msg = Notification_Proxy_Wtr.state.toString
    
    	logInfo( "FILE", "============== Watering starting/stoping ==========")
    	logInfo( "FILE", "Watering_starting/stoping: Stoping watering if already started")
		logInfo( "FILE", "Watering_starting/stoping: msg [" + msg + "]")


		// stop watering if is started. 
     	if(WtrValveFrontStartTimer != null) {
       		WtrValveFrontStartTimer.cancel
        	WtrValveFrontStartTimer = null
        	logInfo( "FILE", "Watering_starting/stoping:  WtrValveFrontStartTimer stoped") 
    	}
    	
    	if(WtrValveFrontStopTimer != null) {
       		WtrValveFrontStopTimer.cancel
        	WtrValveFrontStopTimer = null
        	logInfo( "FILE", "Watering_starting/stoping:  WtrValveFrontStopTimer stoped") 
    	}
    	
     	if(WtrValveBackStartTimer != null) {
       		WtrValveBackStartTimer.cancel
        	WtrValveBackStartTimer = null
        	logInfo( "FILE", "Watering_starting/stoping:  WtrValveBackStartTimer stoped") 
    	}
    	
    	if(WtrValveBackStopTimer != null) {
       		WtrValveBackStopTimer.cancel
        	WtrValveFrontStopTimer = null
        	logInfo( "FILE", "Watering_starting/stoping:  WtrValveFrontStopTimer stoped") 
    	}

     	if(WtrValveLineStartTimer != null) {
       		WtrValveLineStartTimer.cancel
        	WtrValveLineStartTimer = null
        	logInfo( "FILE", "Watering_starting/stoping:  WtrValveLineStartTimer stoped") 
    	}
    	
    	if(WtrValveLineStopTimer != null) {
       		WtrValveLineStopTimer.cancel
        	WtrValveLineStopTimer = null
        	logInfo( "FILE", "Watering_starting/stoping:  WtrValveLineStopTimer stoped") 
    	}

    	if(WateringStopTimer != null) {
       		WateringStopTimer.cancel
        	WateringStopTimer = null
        	logInfo( "FILE", "Watering_starting/stoping: WateringStopTimer stoped") 
    	}

    	WtrValveFront.sendCommand(OFF_R)
      	WtrValveBack.sendCommand(OFF_R)
     	WtrValveLine.sendCommand(OFF_R)
    	WtrLED.sendCommand(OFF)

		// watering process is stoped
    	isWatering = false
    	
		// should we start watering?
    	if (msg == "START") {
	    	
	    	logInfo( "FILE", "Watering_starting/stoping: starting watering")
	    		    	
			// watering light is on
			WtrLED.sendCommand(ON)

			// watering process is starting
			isWatering = true
				    			
			// setting watering duration - read duration from items
    		var Number durationFront = WtrDurationFront.state as DecimalType
    		var Number durationBack  = WtrDurationBack.state as DecimalType
    		var Number durationLine  = WtrDurationLine.state as DecimalType
    		    		
    		var DateTime startTime = now
    		var DateTime endTime = now		
    
			// setting last watering date - mainly for sitemap
    	    var String NowTime = String::format( "%1$td.%1$tm.%1$tY %1$tH:%1$tM", new Date() )
    	    logInfo( "FILE","Watering_starting/stoping: NowTime[" + NowTime + "]")
	    	WtrLastDate.sendCommand(NowTime)
        
			// scaling factor
    		var Number scaleFactor = WtrScaleFactor.state as DecimalType
    		
			// taking into account scaling factor
    		var int wtrFrontTime = ((durationFront * scaleFactor) / 100).intValue
    		var int wtrBackTime  = ((durationBack  * scaleFactor) / 100).intValue
    		var int wtrLineTime  = ((durationLine  * scaleFactor) / 100).intValue

    		logInfo( "FILE","Watering_starting/stoping: durationFront[" + durationFront + "]")
    		logInfo( "FILE","Watering_starting/stoping: durationBack [" + durationBack+ "]")
    		logInfo( "FILE","Watering_starting/stoping: durationLine [" + durationLine + "]")
			logInfo( "FILE","Watering_starting/stoping: scaleFactor  [" + scaleFactor + "]")
			logInfo( "FILE","Watering_starting/stoping: wtrFrontTime [" + wtrFrontTime + "]")
			logInfo( "FILE","Watering_starting/stoping: wtrBackTime  [" + wtrBackTime + "]")
			logInfo( "FILE","Watering_starting/stoping: wtrLineTime  [" + wtrLineTime + "]")
			logInfo( "FILE","Watering_starting/stoping: WtrRainSensor[" + WtrRainSensor.state + "]")


			// starting watering - front section
    		if (wtrFrontTime > 0) {
    			endTime = startTime.plusMinutes(wtrFrontTime)
				logInfo( "FILE", "Watering_starting/stoping: Watering front starts [" + startTime + "] finish [" + endTime + "]")
    			WtrValveFrontStartTimer  = createTimer(startTime) [| 
    				logInfo( "FILE", "Watering_starting/stoping: WtrValveFront[ON_R]")
   					WtrValveFront.sendCommand(ON_R)
    			]
    			WtrValveFrontStopTimer   = createTimer(endTime) [| 
    				logInfo( "FILE", "Watering_starting/stoping: WtrValveFront[OFF_R]")
   					WtrValveFront.sendCommand(OFF_R)
    			]
    			startTime = endTime.plusMinutes(1)
    		}

			// starting watering - back section
    		if (wtrBackTime > 0) {
    			endTime = startTime.plusMinutes(wtrBackTime)
    			logInfo( "FILE", "Watering_starting/stoping: Watering front back [" + endTime + "] finish [" + endTime + "]")
    			WtrValveBackStartTimer  = createTimer(startTime) [| 
    				logInfo( "FILE", "Watering_starting/stoping: WtrValveBack[ON_R]")
   					WtrValveBack.sendCommand(ON_R)
    			]
    			WtrValveBackStopTimer   = createTimer(endTime) [| 
    				logInfo( "FILE", "Watering_starting/stoping: WtrValveBack[OFF_R]")
   					WtrValveBack.sendCommand(OFF_R)
    			]
    			startTime = endTime.plusMinutes(1)
    		}

			// starting watering - watering line
    		if (wtrLineTime > 0) {
    			endTime = startTime.plusMinutes(wtrLineTime)
				logInfo( "FILE", "Watering_starting/stoping: Watering line starts [" + endTime + "] finish [" + endTime + "]")
    			WtrValveLineStartTimer  = createTimer(startTime) [| 
    				logInfo( "FILE", "Watering_starting/stoping: WtrValveLine[ON_R]")
   					WtrValveLine.sendCommand(ON_R)
    			]
    			WtrValveLineStopTimer   = createTimer(endTime) [| 
    				logInfo( "FILE", "Watering_starting/stoping: WtrValveLine[OFF_R]")
   					WtrValveLine.sendCommand(OFF_R)
    			]
    			startTime = endTime
    		}

			pushover("Watering is started, it will finish at [" + endTime + "]", "OpenHabian",-2)
    		
			// execute some action after watering
    		WateringStopTimer = createTimer(endTime) [| 
    			lastWtrDate = now	// last watering date (it helps us calculate how many days without watering)
    			logInfo( "FILE", "Watering_starting/stoping: ==== Watering ended ====")
    			WtrLED.sendCommand(OFF)
    			isWatering = false
    			EventHist.sendCommand("Watering was finished")
    			pushover("Watering was finished", " OpenHabian",-2)
    		]

    	}
	end
	
	
// Watering algorithm
rule "Watering_algorithm"
    when
      Time cron "0 0 0/1 1/1 * ? *"   //check every hour
    then
	
	// Check if actual hour is equal to watering hour and if watering mode is set to automatic
	var DateTime userStartTime = parse(now.getYear() + "-" + now.getMonthOfYear() + "-" + now.getDayOfMonth() + "T" + WtrStartTime.state + ":00")

    // check hour of watering, watering mode and isWatering 
	if ( ( ( userStartTime.getHourOfDay == now.getHourOfDay()  &&  userStartTime.getMinuteOfHour() == now.getMinuteOfHour() ) || checkUserStartTime == false )
    	&& WtrAuto.state == CLOSED && (isWatering == false)
    ) {
		logInfo( "FILE", " ======= Wheather algorithm  ======= ")
		logInfo( "FILE", "Watering_algorithm: now                       [" + now + "]")
		logInfo( "FILE", "Watering_algorithm: userStartTime             [" + userStartTime + "]")
		logInfo( "FILE", "Watering_algorithm: checkUserStartTime        [" + checkUserStartTime + "]")
		logInfo( "FILE", "Watering_algorithm: WtrAuto.state             [" + WtrAuto.state + "]")
		logInfo( "FILE", "Watering_algorithm: isWatering              	[" + isWatering + "]")
		logInfo( "FILE", "Watering_algorithm: RainSensor.state          [" + WtrRainSensor.state + "]")
		logInfo( "FILE", "Watering_algorithm: lastWtrDate               [" + lastWtrDate + "]")
		logInfo( "FILE", "Watering_algorithm: maxNoWtrDays              [" + maxNoWtrDays + "]")  
    	logInfo( "FILE", "Watering_algorithm: checkWtrRainSensor		[" + checkWtrRainSensor + "]")
    	logInfo( "FILE", "Watering_algorithm: checkUserStartTime		[" + checkUserStartTime + "]")
    	    	
	
		/*
		 * Get information about rainfall forecast 
		 */
		var forecastUrl = openwheatherUrl + "forecast?id=" + cityId + "&APPID=" + APPID + "&units=metric&cnt=" + cnt
		logInfo( "FILE", "Watering_algorithm: Getting wheather forecast [" + forecastUrl + "]")
		
		// Trying get  wheather forecast 3 times (from time to time, first try fail)
		var forecastJson=""
		try {
			forecastJson = sendHttpGetRequest(forecastUrl)
			var getCounter=0
			while ( (forecastJson==null) && (getCounter<3)) {
				forecastJson = sendHttpGetRequest(forecastUrl)
				getCounter++
				logInfo( "FILE", "Watering_algorithm: getCounter[" + getCounter + "]")
			}
		} catch(Throwable t) {
      		logInfo("FILE", "Watering_algorithm: Exception happen during getting heather data")
      		forecastJson = null
   		}
				
		logInfo( "FILE", "Watering_algorithm: forecastJson[" + forecastJson + "]")
		
		if ( !(forecastJson == null)) {	

			// When wheather forecast cannot be downloaded we get NULL
			if( ! (forecastJson=="<code>NULL</code>")) {

				// Extract only section about rainfall, looked at
				// https://community.openhab.org/t/solved-smhi-weather/22300/20
				// For test use http://www.jsonquerytool.com/
				
		  	    var String[] rainValues  = transform("JSONPATH","$.list[*].rain.3h", forecastJson).replace("[", "").replace("]", "").split(",") 
	  			rainfallMm = 0

				// Check if there is at least one 'rain' section i json
				if(rainValues.get(0)!="")	
					for (var i = 0; i < rainValues.length; i++) {
						rainfallMm = rainfallMm  + (Double::parseDouble(rainValues.get(i)))
					}

	  			logInfo( "FILE", "Watering_algorithm: rainfall forecast [" + rainfallMm + "] for next [" + cnt + "] periods, rainfall sum [" + rainfallMm + "]")			
			}	else {
				// could not get rainfall forecast, set rainfall=0
				logInfo( "FILE", "Watering_algorithm: Cannot get rainfall forecast")
				rainfallMm = 0
			}			
		} else {
			logInfo( "FILE", "Watering_algorithm: Wheather forecast is NULL - rainfallMm set to 0mm ")
			rainfallMm = 0
		}		

		// updating rainfall forcast for GUI
		WtrRainfallForecast.sendCommand(rainfallMm)
		var String NowTime = String::format( "%1$td.%1$tm.%1$tY %1$tH:%1$tM", new Date() )
		WtrRainfallForecastDate.sendCommand(NowTime)
		
		logInfo( "FILE", " ======= Wheather forecast ======= ")
		logInfo( "FILE", "Watering_algorithm: rainfallMm         [" + rainfallMm + "]")
        logInfo( "FILE", "Watering_algorithm: minimumReqRainfall [" + minimumReqRainfall + "]")
	
		// check rain sensor
		if ( WtrRainSensor.state == CLOSED || checkWtrRainSensor==false ) {

			logInfo( "FILE", "Watering_algorithm: RainSensor - watering is needed, start watering")
			
			// check rainfall forecast with minimum rainfall
			if (rainfallMm > minimumReqRainfall) {

				// rainfall for next 24h are sufficient, check how many days past since from last watering if more than 
				// maxNoWtrDays start watering anyway
				if ( lastWtrDate.isBefore(now.minusDays(maxNoWtrDays)) ) {
					logInfo( "FILE", "Watering_algorithm: lastWtrDate[" +lastWtrDate+ "] is later than now-maxNoWtrDays[" +maxNoWtrDays+ "]")
					EventHist.sendCommand("Watering is starting")
					Notification_Proxy_Wtr.sendCommand("START")
				} 
				
			} else {
				// rainfall forecast is not enough 
				missingRainfall = minimumReqRainfall - rainfallMm
				logInfo( "FILE", "Watering_algorithm: lack of  [" + missingRainfall + "] mm")
				EventHist.sendCommand("Watering is starting")
				Notification_Proxy_Wtr.sendCommand("START")			
			}	
			logInfo( "FILE", " ======= End of Watering algorithm  ======= ")
		} else {
			// rain sensor is wet 
			lastWtrDate = now 
			logInfo( "FILE", "Watering_algorithm: watering not started, RainSensor - watering is not needed")
		}	
	} else {
		logInfo( "FILE", "Watering_algorithm: not started. checkUserStartTime["  +checkUserStartTime+ "] WtrAuto.stat["
			+WtrAuto.state + "] isWatering[" +isWatering+ "] WtrRainSensor.state[" + WtrRainSensor.state + "]"
		)
	}
end

Mapping

RainSensor_PL.map

OPEN=Wet
CLOSED=Dry
null=Unknown

Valve_PL.map

ON=Open
OFF=Closed
NULL=Unknown

WtrAuto_PL.map

OPEN=Not active
CLOSED=Active
null=Unknown

Sitemap

sitemap default label="Watering"
{		
        Text label="Watering" icon="water" {
			Frame label="Sections" {
				Switch item=WtrValveFront label="Front"   mappings=[OFF="Wł", ON="Wył"] 
				Switch item=WtrValveBack  label="Back"     mappings=[OFF="Wł", ON="Wył"] 
				Switch item=WtrValveLine  label="Line"   mappings=[OFF="Wł", ON="Wył"] 
			}
			
			Frame label="Status" {
				Text item=WtrRainSensor 
				
				// watering automation
				Text item=WtrAuto 
				
				// rainfall forecast
				Text item=WtrRainfallForecast
				
				// last date rainfall wheather forecast
				Text item=WtrRainfallForecastDate
				
				// last watering date
				Text item=WtrLastDate
		
			}	
 		}  
 		
 		Text label="Configuration" icon="settings" {
 			
 			Text label="Watering" icon="water" {
 			
	 			Frame label="Watering times" {
		 			Setpoint item=WtrDurationFront  label="Front" minValue=1 maxValue=40 step=5
		 			Setpoint item=WtrDurationBack   label="Back" minValue=1 maxValue=40 step=5
		 			Setpoint item=WtrDurationLine   label="Watering line" minValue=1 maxValue=40 step=5
		 			Setpoint item=WtrScaleFactor    label="Scale factor" minValue=1 maxValue=100 step=5
		 			
	 			}
	 			Frame label="Misc" {
	 			Selection item=WtrStartTime mappings=["00:00"="00:00", "01:00"="01:00", "02:00"="02:00",
	 				                                  "03:00"="03:00", "04:00"="04:00", "05:00"="05:00", 
	 				                                  "06:00"="06:00", "07:00"="07:00", "08:00"="08:00",
	 				                                  "09:00"="09:00", "10:00"="10:00", "11:00"="11:00", 
	 				                                  "12:00"="12:00", "13:00"="13:00", "14:00"="14:00",
	 				                                  "15:00"="15:00", "16:00"="16:00", "17:00"="17:00",
	 				                                  "18:00"="18:00", "19:00"="19:00", "20:00"="20:00", 
	 				                                  "21:00"="21:00", "22:00"="22:00", "23:00"="23:00"
	 			]
	 			}
 				
 			}
 			
 		}

 }
20 Likes

UUuhhh thanks for sharing! :heart: :+1:

1 Like

Nice, I am ordering one right now

For this installation I’ve designed PCB - 16 input , 16 output for one PCB but it can be extended for next PCB.
It looks that

Nice, do you want to improove it and sell it?

Probably next month I will order more PCB and if someone is interested I can sell it. Because I plan to order only small number, price is quite high around 4-5$ per PCB + cost of components.

1 Like

can you add holes for din clamp and add gnd to each gpio?

How din clamps look like? Adding GND to each I/O can be difficult PCB is only 80x100mm (limit of free Eagle) .

thanks for sharing. I’ve some comments.

Do not include these imports in OH 2.x

This could be just

Item RainSensor changed to OPEN

This could be just

Item RainSensor changed to CLOSED

Where is the definition of the RainSensor Item? I do not see it above in your items listing. Assuming it is a Contact, which is what it must be to use the from and to like this. Note the CLOSED and not CLOSE.

This applies across the whole rules file.

You could combine the two rules and save some lines of code if you wanted to.

Everything else was translated to English.

This could probably be significantly reduced by using a Map<String,Timer> and then looping through the members rather than reproducing the same lines of code over and over.

Move these to the top. You don’t want an error in the previous code to prevent your valves from turning off.

This can be reduced to

WtrLastDate.sendCommand(new DateTimeType)

You could use Design Pattern: Cascading Timers to make this part more generic which would make it easier for other users to adopt.

Without that, a lambda would be useful here to avoid duplicating code for each zone.

Could use a Timer for this rather than a polling cron trigger.

I would recommend using one of the Weather bindings for this rather than using HTTP directly. Or if it were acquired separately using HTTP, move this to a special weather set of rules so you don’t need to modify your irrigation rules if you choose to use a different weather service.

Thanks for sharing this example. There were only a few bugs and a few opportunities to make the code a little more generic.

1 Like

I would recommend to go for smd components(seedsudio among others can solder the pcb)

Maype the GPIO also should be optocoupled for the inputs

import org.openhab.core.library.types.*

import org.joda.time.*

Do not include these imports in OH 2.x

You are right but it helps when I develope code in SmartDesigne , without these import it displays errors.

You could use Design Pattern: Cascading Timers to make this part more generic which would make it >easier for other users to adopt.

I’ve almost rewrote whole code to use this pattern, after this change code is shorter about 100 lines but is definitly more complicated to understand and more complicated to debug. My watering installation has only 3 section in my situation what I’ve written is a trade off between flexibility and simplicity of code.
For sure I will put all timers to Map and I will use lambda expression and fix minor bugs. To be honest I’ve made all modification but I have to make final test :slight_smile:

Could use a Timer for this rather than a polling cron trigger.

I thought about Timers, but pooling has some adventages - it is simpler to test.

I would recommend using one of the Weather bindings for this rather than using HTTP directly. Or if it were acquired separately using HTTP, move this to a special weather set of rules so you don’t need to m

Good idea, but I have to check if this bindings offers exactly what I want - I need rainfall for next 24h

It shouldn’t since org.openhab.core.library.types no longer exist. Trying to include it should do nothing. Are you using Eclipse SmartHome Designer 0.8? Furthermore, using * in an import statement will generate a warning both in ESH Designer and in the OH logs.

There is often a trade-off between making code generic which requires the code to become a little more abstract. Abstract does not necessarily mean more complicated, but it is not always as straightforward. However, though it requires a little more work up front to understand, if done right, it becomes much easier to maintain and modify in the future.

You only have three zones but at least where I’ve lived, 5 or more is more typical (I have nine). For this reason, your example becomes much easier to adopt by other users if they can add new zones without needing to modify the rules. And this is primarily why I made the suggestion.

I don’t see Timers as any harder to test than polling.

I know for sure the Wunderground binding does. I beleive most if not all of the other bindings, except for maybe the Yahoo Weather Binding also offer this.

There is often a trade-off between making code generic which requires the code to become a little more >abstract

Yes you are right. For 15 years I was software developers and I know what do you mean.
But in my situation I wrote software for my garden not generic garden :slight_smile: Second, I think we have many users on forum with limited knowledge about programming and they probably do not understand more advanced patterns and they can have problems to adopt code to specific needs. Usually people looking for what is easy to understand and easy use.
Your cascade pattern is more difficult to understand but off course for several section you are right is definitely better.
From my experience I can say that in some situation simpler code is better when you think about maintenance.

I don’t see Timers as any harder to test than polling.

Maybe not harder but simpler for me. During pooling I execute so little code that is also does not generate performance issue and I can exacly change how often my action can be performed.

But still thank you for your help , your advice is very helpful.

Michal

1 Like

True, but you posted it in Tutorials & Examples Solutions which implies you intended this posting to be something that others could take, use, and adopt to their system. With that assumption, I was looking for ways to make a user’s adoption of the the code require less work and less overall understanding. If the user need only copy the rule and create a bunch of Items and Groups it takes even less coding knowledge for them than requiring them to create new global variables and understand all the rules so they know where to copy and paste the right sections of code to add more zones.

But this section of the forum is also for the “I made this” type postings which your posting and I do thank you for contributing.

Except when you violate DRY you end up with a lot more opportunities to mess up when you want to change some behavior, fix a problem, or add more zones because you have to make the same changes in multiple places.

I agree that simpler code is easier to maintain, but I disagree that more abstract code is necessarily more complex in this regard.

1 Like

Dear Mr. Rich Koshak, Good Morning. I request you to publish your method of coding for a similar or a comparable Watering System for me Adapt my needs. I will also learn lot of coding technique from your posting.
Please help us with your for a learner like me.
Thanking you in advance.
With Great Regards,
Ragothaman R

Thank you so much and with Best regards.
Ragothaman R.

BTW includes are not necessary in 2.1 but I have strange behaviour in rule:

It does not work

var tmp= new Date()

it produce such error

2017-11-03 21:26:00.448 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule Watering_algorithm: An error occurred during the script execution: null

It works

var tmp= new java.util.Date()

and it also works

import java.util.Date
var tmp= new Date()

What imports are needed and which are not needed?

Only those imports I highlighted are not needed. I.e. those from org.openhab.core and org.joda. All other imports are required, including those from java.util.

How is it going with this project?
I’m looking for watering system for my house. Valeves are in place. I have older control system but without external connection. Still have few months to next seeson but it would be nice to plan something.