Small and simple irrigation solution - OH3

I have a 50m2 garden with sprinklers and drip irrigation. Planning and installation were done by my gardener (approx cost 360€ for 4x sprinklers, 3x electrovalves and their box, 30m drip irrigation, other tubes and installation).

I’ve built the controller (approx cost 50€) using these components:

  1. 1x Sonoff 4CH Pro R2
  2. 1x 24VAC transformer
  3. 1x ip65 box

The above costs do not include water socket and wifi infrastructure. I already had them in place. Water pressure is 3bar so it was possible to group 2 sprinklers per electrovalve. My connection scheme to sonoff relays is:

  1. One relay manages the 24VAC transformer. This is mainly to enlarge its usefull life but it also has a small benefit in the electricity bill
  2. Another relay manages the electrovalve connected to the drip irrigation
  3. The remainning two relays manage one electrovalve each, and each of these valves connect to two sprinklers

Scheme


The box is installed in the garage (valves are in another box under the ground, behind the wall shown in the picture)

I choose Sonoff 4CH Pro because of its ability to manage different voltages in each relay. I’ve flashed it with Tasmota. One very interesting Tasmota parameter is PulseTime. I use it to limit irrigation times even if wifi goes down.

Usually irrigation is done daily at sunrise. Of course I do not want to waste water so I use the following logic in my rules:

  1. When it rains irrigation is interrupted (if active). If it rains too much the interval between irrigations can be enlarged by up to 3 days
  2. Irrigation can be delayed or performed in half-time depending on humidity
  3. If it’s too windy irrigation is done in the drip system only (I use sprinklers for the grass and it does not require daily irrigation)
  4. Irrigation will not take place if there is a rain forecast. Of course forecasts can be false, so if it does not rain for long I will force an irrigation

I am fortunate that OpenWheather produces reliable forecasts for my location, otherwise I would have to acquire sensors (rain, and probably wind).

Therefore my setup requires the following add-ons:

  1. Astro - to determine sunrise
  2. MQTT - to drive the Sonoff
  3. OpenWheatherMap - for rain/wind/humidity actuals and forecasts
  4. MapDB - to keep critical info whenever I need to restart OpenHab
  5. RRDj4 - to calculate accumulated rain

So here are my OH3 control files:

Version 1

astro.things

astro:sun:local "Posição do Sol" [geolocation="40.638900729572036,-8.648887092899434,30", interval=60]

bridge.things

//
// MQTT
//

Bridge mqtt:broker:aveiro "$Controller MQTT" [ 
  host="192.168.130.1",
  port=1883,
  secure="AUTO",
  qos=0,
  retain=true,
  keep_alive_time=30000,
  reconnect_time=60000,
  username="*****",
  password="****"
]

//
// OPenWheatherMap
//

Bridge openweathermap:weather-api:api "$OpenWeatherMap" [apikey="****", refreshInterval=120, language="pt"]

relay.things

//
// Sonoff
//

Thing mqtt:topic:sonoff_rega "Rega do Quintal" (mqtt:broker:aveiro) {
    Channels:
        Type switch : zona1     "Zona 1"               [stateTopic="quintal/stat/rega/POWER1", commandTopic="quintal/cmnd/rega/POWER1"]
        Type string : zona1p    "Tempo 1"              [commandTopic="quintal/cmnd/rega/PulseTime1"]
        Type switch : zona2     "Zona 2"               [stateTopic="quintal/stat/rega/POWER2", commandTopic="quintal/cmnd/rega/POWER2"]
        Type string : zona2p    "Tempo 2"              [commandTopic="quintal/cmnd/rega/PulseTime2"]
        Type switch : zona3     "Zona 3"               [stateTopic="quintal/stat/rega/POWER3", commandTopic="quintal/cmnd/rega/POWER3"]
        Type string : zona3p    "Tempo 3"              [commandTopic="quintal/cmnd/rega/PulseTime3"]
        Type switch : power     "Transformador 24V"    [stateTopic="quintal/stat/rega/POWER4", commandTopic="quintal/cmnd/rega/POWER4"]
        Type string : powerp    "Tempo limite"         [commandTopic="quintal/cmnd/rega/PulseTime4"]
}

In OH3 it’s preferrable to define things using the Basic UI. In this case here is an example

UID: mqtt:topic:aveiro:sonoff_rega
label: Rega do Quintal
thingTypeUID: mqtt:topic
configuration:
  payloadNotAvailable: Offline
  availabilityTopic: tele/rega/LWT
  payloadAvailable: Online
bridgeUID: mqtt:broker:aveiro
channels:
  - id: zona1
    channelTypeUID: mqtt:switch
    label: Zona 1
    description: ""
    configuration:
      commandTopic: cmnd/rega/POWER1
      stateTopic: stat/rega/POWER1
  - id: zona2
    channelTypeUID: mqtt:switch
    label: Zona 2
    description: ""
    configuration:
      commandTopic: cmnd/rega/POWER2
      stateTopic: stat/rega/POWER2
  - id: zona3
    channelTypeUID: mqtt:switch
    label: Zona 3
    description: ""
    configuration:
      commandTopic: cmnd/rega/POWER3
      stateTopic: stat/rega/POWER3
  - id: zona4
    channelTypeUID: mqtt:switch
    label: Zona 4
    description: ""
    configuration:
      commandTopic: cmnd/rega/POWER4
      stateTopic: stat/rega/POWER4
  - id: zon1p
    channelTypeUID: mqtt:number
    label: Pulsetime 1
    description: null
    configuration:
      commandTopic: cmnd/rega/PulseTime1
      stateTopic: stat/rega/RESULT
      transformationPattern: REGEX:(.*PulseTime1.*)∩JSONPATH:$.PulseTime1.Set
  - id: zon2p
    channelTypeUID: mqtt:number
    label: Pulsetime 2
    description: null
    configuration:
      commandTopic: cmnd/rega/PulseTime2
      stateTopic: stat/rega/RESULT
      transformationPattern: REGEX:(.*PulseTime2.*)∩JSONPATH:$.PulseTime2.Set
  - id: zon3p
    channelTypeUID: mqtt:number
    label: Pulsetime 3
    description: null
    configuration:
      commandTopic: cmnd/rega/PulseTime3
      stateTopic: stat/rega/RESULT
      transformationPattern: REGEX:(.*PulseTime3.*)∩JSONPATH:$.PulseTime3.Set
  - id: zon4p
    channelTypeUID: mqtt:number
    label: Pulsetime 4
    description: null
    configuration:
      commandTopic: cmnd/rega/PulseTime4
      stateTopic: stat/rega/RESULT
      transformationPattern: REGEX:(.*PulseTime4.*)∩JSONPATH:$.PulseTime4.Set

irrigation.items

//
// Parâmetros para rega automática
//

Group gRega "Rega" <lawnmower> (OU_Terrace)
Group gIrrigation
DateTime OU_Irrigation_Last "Last irrigation (or rain) [%1$td/%1$tm %1$tR]" <calendar> (gRega)
DateTime OU_Irrigation_Min "Irrigation disabled before [%1$td/%1$tm %1$tR]" <calendar> (gRega)
DateTime OU_Irrigation_Max "Irrigation mandatory after [%1$td/%1$tm %1$tR]" <calendar> (gRega)

//
// Comando da rega
//

Switch OU_Terrace_Irrigation_Power "Transformer [%s]" (gIrrigation,gRega) {channel="mqtt:topic:sonoff_rega:power"}
String OU_Terrace_Irrigation_Power_Time "Tempo" {channel="mqtt:topic:sonoff_rega:powerp"}
Switch OU_Terrace_Irrigation_Zone1 "Drip system" <faucet> (gIrrigation,gRega) {channel="mqtt:topic:sonoff_rega:zona1"}
String OU_Terrace_Irrigation_Zone1_Time "Tempo" {channel="mqtt:topic:sonoff_rega:zona1p"}
Switch OU_Terrace_Irrigation_Zone2 "Sprinklers NW" <faucet> (gIrrigation,gRega) {channel="mqtt:topic:sonoff_rega:zona2"}
String OU_Terrace_Irrigation_Zone2_Time "Tempo" {channel="mqtt:topic:sonoff_rega:zona2p"}
Switch OU_Terrace_Irrigation_Zone3 "Sprinklers SO" <faucet> (gIrrigation,gRega) {channel="mqtt:topic:sonoff_rega:zona3"}
String OU_Terrace_Irrigation_Zone3_Time "Tempo" {channel="mqtt:topic:sonoff_rega:zona3p"}

//
// Meteorologia
//

Group:Number:AVG           gRain (Home)
Number OU_Weather_Actual_Rain "Actual rain" <rain> (gIrrigation) {channel="openweathermap:weather-and-forecast:api:local:current#rain"}
Number OU_Weather_Actual_Rain_avg "Average rain [%.2f mm]" <rain> (gRega)
Number OU_Weather_Prev_Rain_3h "Rain forecats 3h" <rain> (gIrrigation, gRain) {channel="openweathermap:weather-and-forecast:api:local:forecastHours03#rain"}
Number OU_Weather_Prev_Rain_6h "Rain forecats 6h" <rain> (gIrrigation, gRain) {channel="openweathermap:weather-and-forecast:api:local:forecastHours06#rain"}
Number OU_Weather_Prev_Rain_9h "Rain forecats 9h" <rain> (gIrrigation, gRain) {channel="openweathermap:weather-and-forecast:api:local:forecastHours09#rain"}
Number OU_Weather_Prev_Rain_12h "Rain forecats 12h" <rain> (gIrrigation, gRain) {channel="openweathermap:weather-and-forecast:api:local:forecastHours12#rain"}
Number OU_Weather_Prev_Rain_15h "Rain forecats 15h" <rain> (gIrrigation, gRain) {channel="openweathermap:weather-and-forecast:api:local:forecastHours15#rain"}
Number OU_Weather_Actual_Wind "Actual wind" <rain> (gIrrigation) {channel="openweathermap:weather-and-forecast:api:local:current#wind-speed"}
Switch OU_Weather_Actual_Rain_high
Number OU_Weather_Prev_Humidity "Humidity forecast" <humidity> (gIrrigation) {channel="openweathermap:weather-and-forecast:api:local:forecastHours06#humidity"}

mapdb.persist

//
// Requer persistence MapDB
//

Strategies {
    default = everyUpdate
}
Items {
	OU_Irrigation_Last			: strategy = everyUpdate, restoreOnStartup
	OU_Irrigation_Min			: strategy = everyUpdate, restoreOnStartup
	OU_Irrigation_Max			: strategy = everyUpdate, restoreOnStartup
}

rrdj4.persist

//
// Requer persistence rrd4j
//

Strategies {
    // for rrd charts, we need a cron strategy
    everyMinute : "0 * * * * ?"
}

Items {
    // persist items on every change and every minute
    OU_Weather_Actual_Rain      : strategy = everyMinute
}

The switching sequence is the following:

  1. If it’s too windy Relay 4 → Relay 1
  2. Otherwise Relay 4 → Relay 2 → Relay 3 → Relay 1

Garden.rules

rule "Calculate average rain 2h"
when
    Time cron "0 0/15 * 1/1 * ? *"
then
    OU_Weather_Actual_Rain_avg.postUpdate(OU_Weather_Actual_Rain.averageSince(now().minusMinutes(120),"rrd4j"))
end

rule "Sunrise irrigation"
when
//  for test purposes  Time cron "0 11 16 ? * * *" or
    Channel 'astro:sun:local:rise#event'   triggered START or       // Normally astro binding is available
    Time cron "0 30 8 ? * * *"                                      // But if it fails irrigation will take place @ 08:30
then

    // Decide if irrigation will take place in this cycle
    var decision = "2"                                      // 0=none   1=drop only     2=full
    val Number n_now = now.toInstant.toEpochMilli
    val Number n_min = (OU_Irrigation_Min.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
    val Number n_max = (OU_Irrigation_Max.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli
    if (n_now >= n_max) {                                   // Force irrigation if last irrigation done too long ago
        decision = "2"
	} else if (n_now <= n_min) {                            // No need to irrigate, last irrigation was very recent
        decision = "0"
	} else if (gRain.state > 0) {                           // No need to irrigate, it's going to rain
        decision = "0"
	} else if (OU_Weather_Prev_Humidity.state >= 95) {      // No need to irrigate, very high humidity prevision
        decision = "0"
	} else if (OU_Weather_Actual_Wind.state > 5) {          // It's too windy, let's irrigate drop only
        decision = "1"
    }

    if(decision != "0") {

        // Decide irrigation time based on forecasted humidity
        // PulseTime = 100 + 60 * minutes
        if (OU_Weather_Prev_Humidity.state >= 80){              // Irrigation times for high humidity
            OU_Terrace_Irrigation_Zone1_Time.sendCommand(250)   // PulseTime 2.5 minutes
            OU_Terrace_Irrigation_Zone2_Time.sendCommand(400)   // PulseTime 5 minutes
            OU_Terrace_Irrigation_Zone3_Time.sendCommand(400)   // PulseTime 5 minutes
	        OU_Terrace_Irrigation_Power_Time.sendCommand(880)   // PulseTime 13 minutes
        } else {                                                // Irrigation times for low humidity
	        OU_Terrace_Irrigation_Zone1_Time.sendCommand(400)   // PulseTime 5 minutes
	        OU_Terrace_Irrigation_Zone2_Time.sendCommand(700)   // PulseTime 10 minutes
            OU_Terrace_Irrigation_Zone3_Time.sendCommand(700)   // PulseTime 10 minutes
	        OU_Terrace_Irrigation_Power_Time.sendCommand(1630)  // PulseTime 25.5 minutes
        }

        // Start irrigation cycle
        OU_Terrace_Irrigation_Power.sendCommand(ON)             // Switch on transformer
        createTimer(now().plusSeconds(1),  [ |                  // Wait 1 sec before starting irrigation
        	if(decision == "1") {
                OU_Terrace_Irrigation_Zone1.sendCommand(ON)     // turn on valve for drip irrigation
            } else {
                OU_Terrace_Irrigation_Zone2.sendCommand(ON)     // turn on valve for North East sprinklers
                val String a1 = now.toInstant.toString
                OU_Irrigation_Last.postUpdate(a1)               // set irrigation datetime
            }
        ])
    }
end

rule "South West sprinklers"
when
    Item OU_Terrace_Irrigation_Zone2 changed from ON to OFF
then
    if (OU_Terrace_Irrigation_Power.state == ON){
	    OU_Terrace_Irrigation_Zone3.sendCommand(ON)         // turn on valve 3
    }
end

rule "Drip irrigation"
when
    Item OU_Terrace_Irrigation_Zone3 changed from ON to OFF
then
    if (OU_Terrace_Irrigation_Power.state == ON){
	    OU_Terrace_Irrigation_Zone1.sendCommand(ON)         // turn on valve 1
    }
end

rule "Stop irrigation"
when
    Item OU_Weather_Actual_Rain changed or
    Item OU_Terrace_Irrigation_Zone1 changed from ON to OFF
then

    // Set irrigation control variables
    if(OU_Terrace_Irrigation_Power.state == ON) {
        OU_Weather_Actual_Rain_high.sendCommand(OFF)
        val a2 = new DateTimeType(now().plusHours(22).toInstant.toString)
        OU_Irrigation_Min.postUpdate(a2)                        // set low interval for next irrigation
        val a3 = new DateTimeType(now().plusHours(70).toInstant.toString)
        OU_Irrigation_Max.postUpdate(a3)                        // set high interval for next irrigation
	} else if (OU_Weather_Actual_Rain_avg.state >= 0.1 || OU_Weather_Actual_Rain_high.state == ON) {
        OU_Weather_Actual_Rain_high.sendCommand(ON)
        val String a1 = now.toInstant.toString
        OU_Irrigation_Last.postUpdate(a1)                       // set irrigation datetime
        val a2 = new DateTimeType(now().plusHours(70).toInstant.toString)
        OU_Irrigation_Min.postUpdate(a2)                        // set low interval for next irrigation
        val a3 = new DateTimeType(now().plusHours(118).toInstant.toString)
        OU_Irrigation_Max.postUpdate(a3)                        // set high interval for next irrigation
	} else {
        val String a1 = now.toInstant.toString
        OU_Irrigation_Last.postUpdate(a1)                       // set irrigation datetime
        val a2 = new DateTimeType(now().plusHours(46).toInstant.toString)
        OU_Irrigation_Min.postUpdate(a2)                        // set low interval for next irrigation
        val a3 = new DateTimeType(now().plusHours(94).toInstant.toString)
        OU_Irrigation_Max.postUpdate(a3)                        // set high interval for next irrigation
    }
    OU_Terrace_Irrigation_Power.sendCommand(OFF)
end
</details>
5 Likes

I have a simple setup with two zones ON/OFF via zwave relay.

I am trying to rewrite this for use on that. Do you have any sitemap setup?

I’ve converted all things/items definitions from conf to OH3’s Basic UI and I’m no longer using sitemaps. Now I’m using OH3’s Overview page and all my items are grouped in locations, equipment and properties.

I will update this thread with my new definitions but unfortunately OH3 item/group definitions in Basic UI are not easy to share, and these are what drive the automatic construction of locations, equipment and properties pages.

This is my backyard

Could you please share some pictures of your sonoff / transformer and the box?

First post updated

1 Like

I am building one this weekend. I’ve bought a Nov’Elec 13 module DIN cabinet (14€) and Vemer vn319000 24V 30VA DIN transformer (35€). I have one Claber solenoid valve but will add more after testing. I figured that the Sonoff takes 9 slots, the transformer 2, hopefully leaving one for a circuit breaker. Clearly this is for a box installed inside.

Good idea.

I’m also planning the following enhancements:

  1. Program the irrigation sequence using tasmota rules. OH will calculate pulsetimes and trigger a complete (or partial) cycle, tasmota rules will assure the irrigation sequence even if OH/wifi breaks during irrigation. RF commands could also be used to trigger irrigation
  2. Use a soil moisture sensor. This would allow automatic calibration of irrigation times according to soil humidity, and postpone irrigation for longer periods than my current algorithm does (presently I do not know if the soil is saturated with water so that irrigation can be postponed for more than 4 days)

Here is the box I’ve just made (and tested) as described above.



The three cores at the output are for 2 valves (black and green/yellow each to one side of a valve, and the blue the common to the other side of both valves).
Temporary mains lead for testing.
It’s a 20A breaker because that is what I had lying around. I’ll test the current when it is complete and buy one the right size, I guess probably 2A.

Hi, my dear irrigating friends =)
Im going to use my MegaD-2561-31I15O-RTC-PoE Monoblock for watering - 14 16A relays will be enough for me forever =) For circuit protection you can check automotive stores - there are often 2A fuses, ive never seen less than 6A circuit breakers in hardware stores (they do exist in manufacturers lists), so a fuse for auto is the easiest and cheapest protection for us.
I believe, there should be some watering correction based on temperature and rain forecast. In Hi-Garden Aquarius watering system there were corrections based on temperature - they worked rather well for me after some experiments.

Here’s what I got at my local hardware store (2A 4€):
image

However, I’m thinking to put in fuse on the transformer secondary as well, as I think the transformer could fry before the breaker trips in case of a short.

@moody_blue I don’t quite follow the logic of your Stop Irrigation rule. You trigger the rule on either the completion of the irrigation sequence or any change in rain (start, stop, increase, decrease). Then your first condition in the script tests for power on, i.e. either the irrigation sequence has completed or there is some change in rainfall while irrigation is on. Surely you would turn off based on average rain being above some threshold, or you risk repeated meaningless rain starving your plants of water? Or did I not understand it properly? Or maybe you don’t get weather like that?

What is the purpose of OU_Weather_Actual_Rain_high? Is it purely to show in the UI that there has been meaningful rain? The condition in the script appears to have to effect of turning it on when there is meaningful rain, and keeping it on while there are any rainfall changes of any magnitude, continually delaying the next irrigation. It is only if there are no rainfall changes that it will allow irrigation to go ahead based on OU_Irrigation_Max no longer being prolonged.

And the final else condition also seems to perpetually delay irrigation if you are having a spell of bad weather with minor occasional rain, in this case without breaching the threshold to turn on OU_Weather_Actual_Rain_high.

So, if I have followed, this will work if your weather pattern is meaningful rain then nothing. If instead you get frequent low volume rain (not enough to nourish the plants), the logic may need adjusting.

Thanks for your help.

I have changed the rule in my setup, removing the test for the transformer being on, and will change my first post accordingly.

Tricky question. Without moisture sensors it’s hard to decide if the soil is dry enough to justify irrigation. I plan to aquire some moisture sensors to take better decisions.

Until then I consider “light rain” the same as a normal irrigation cycle. The main difference is that irrigation cycles only trigger at sunrise (or 8:30 if astro binding is inoperative for some reason) and “light rain” may occur at any hour. So, in fact, when there is “light rain”, the next irrigation cycle is skipped,

But if there is “heavy rain” I consider that the soil is saturated with water and so I turn on OU_Weather_Actual_Rain_high and increase the interval between irrigations. If rain (“heavy” or “light”) occurs the high interval continues. The only event that can set OU_Weather_Actual_Rain_high to OFF is an actual irrigation cycle.

Someone suggested me to use evapotranspiration stats to estimate soil moisture level instead of using moisture sensors, but I don’t know the mathematics to use such info. Furthermore this data is not published on mondays (in my Country) which would further complicate the algorithm to estimate soil moisture.

1 Like