GrowBuddy v1.0
GrowBuddy v1.0 is intended to help you automate growing your plants indoor.
It takes care of :
- growlight automation
- automated watering
- circular air
- ventilation
Requirements:
- JSScripting (ECMA2021) enabled for the additional Rule which interacts with the widget (or you can write your own rule in your preferred Rule-language)
- Additional items for storing several values (startTime, endTime, wateringInterval, wateringDuration, growPhase etc…)
- Four Smartplugs for the devices you want to control (growlight, circular-air, air-ventilation, watering-pump)
…and of course the physical equipment to connect it to your Smartplugs ;)…
After following this guide…
you should be able to relax and enjoy your automated grow experience, the widget takes care of enabling and disabling your GrowLight - you only have to adjust your desired StartTime and the widget automatically chooses the EndTime according to the selected GrowPhase (18 hours for Grow and 12 hours for Flowering period). You can choose a WateringInterval between 5 minutes and 1 day in certain steps and a WateringDuration between 5 and 90 seconds. The AirCirculation automatically starts with the GrowLight and stops when it turns off, the Ventilation runs 24/7 except the MainSwitch is turned off, which automatically disables all Smartplugs. You Only have to take care of your WaterReservoir ;)…
The Rules File (ECMA2021)
Save this file as growbuddy.js
in the /automation/js/
folder.
const { items, rules, triggers } = require("openhab");
// timeouts
let wateringTimer;
const mainswitch = items.getItem("GrowbuddyMainSwitch");
const growbuddylight = items.getItem("SmartplugGrowlight");
const growbuddyventilation = items.getItem("SmartplugVentilation");
const growbuddyairfan = items.getItem("SmartplugCirculation");
const growbuddywater = items.getItem("SmartplugWatering");
const growbuddywatering = items.getItem("GrowbuddyWatering");
const growbuddywaterinterval = items.getItem("GrowbuddyWaterInterval");
const growbuddywaterdura = items.getItem("GrowbuddyWaterDuration");
const lightstart = items.getItem("GrowbuddyLightStart");
const lightstop = items.getItem("GrowbuddyLightStop");
const growphase = items.getItem("GrowbuddyGrowPhase");
rules.JSRule({
name: "GrowBuddy - Rule Disabler",
description: "disable rules when mainswitch is turned off",
triggers: [triggers.ItemStateChangeTrigger("GrowbuddyMainSwitch")],
execute: (data) => {
if (mainswitch.state === "OFF") {
growbuddylight.sendCommandIfDifferent("OFF");
growbuddyventilation.sendCommandIfDifferent("OFF");
growbuddyairfan.sendCommandIfDifferent("OFF");
rules.setEnabled(growbuddyTogglelight, false);
rules.setEnabled(growbuddyToggleairfan, false);
rules.setEnabled(growbuddyToggleventilation, false);
rules.setEnabled(growbuddyTogglewatering, false);
} else if (mainswitch.state === "ON") {
rules.setEnabled(growbuddyTogglelight, true);
rules.setEnabled(growbuddyToggleairfan, true);
rules.setEnabled(growbuddyToggleventilation, true);
rules.setEnabled(growbuddyTogglewatering, true);
}
},
tags: [],
});
rules.JSRule({
name: "Growbuddy - Toggle Light",
description: "schedule light for growbuddy",
triggers: [
triggers.ItemStateChangeTrigger("GrowbuddyMainSwitch"),
triggers.DateTimeTrigger("GrowbuddyLightStart", false),
triggers.DateTimeTrigger("GrowbuddyLightStop", false),
],
execute: (data) => {
let now = time.ZonedDateTime.now();
let curr = now.isBetweenDateTimes(lightstart, lightstop) ? "ON" : "OFF";
if (growbuddylight.state !== curr) {
growbuddylight.sendCommandIfDifferent(curr);
console.log("Growbuddy - Switching Lights", curr);
}
},
tags: [],
id: "growbuddyTogglelight",
});
rules.JSRule({
name: "GrowBuddy - Toggle Airfan",
description: "schedule airfan for growbuddy",
triggers: [
triggers.ItemStateChangeTrigger("GrowbuddyMainSwitch"),
triggers.ItemStateChangeTrigger("SmartplugGrowlight"),
triggers.ItemStateChangeTrigger("GrowbuddyGrowPhase"),
],
execute: (data) => {
if (growbuddylight.state === "ON") {
growbuddyairfan.sendCommandIfDifferent("ON");
} else growbuddyairfan.sendCommandIfDifferent("OFF");
},
tags: [],
id: "growbuddyToggleairfan",
});
rules.JSRule({
name: "GrowBuddy - Toggle Ventilation",
description: "schedule ventilation for growbuddy",
triggers: [
triggers.ItemStateChangeTrigger("GrowbuddyMainSwitch"),
triggers.ItemStateChangeTrigger("SmartplugGrowlight"),
],
execute: (data) => {
if (mainswitch.state === "ON") {
growbuddyventilation.sendCommandIfDifferent("ON");
}
},
tags: [],
id: "growbuddyToggleventilation",
});
rules.JSRule({
name: "Growbuddy - Schedule Watering",
description: "schedule watering for growbuddy",
triggers: [triggers.DateTimeTrigger("GrowbuddyWatering")],
execute: (data) => {
const interval = time.toZDT(growbuddywaterinterval);
const duration = time.toZDT(growbuddywaterdura).getMillisFromNow();
if (wateringTimer) clearTimeout(wateringTimer);
wateringTimer = null;
growbuddywater.sendCommandIfDifferent("ON");
growbuddywatering.sendCommandIfDifferent(interval);
wateringTimer = setTimeout(fn_Watering, duration);
function fn_Watering() {
growbuddywater.sendCommandIfDifferent("OFF");
clearTimeout(wateringTimer);
wateringTimer = null;
}
},
tags: [],
id: "growbuddyTogglewatering",
});
rules.JSRule({
name: "Growbuddy - Update Watering Time if MainSwitch was activated",
description: "update watering time when mainswitch is turned on",
triggers: [
triggers.ItemStateChangeTrigger("GrowbuddyMainSwitch", undefined, "ON"),
triggers.SystemStartlevelTrigger(100),
],
execute: (data) => {
if (time.toZDT(growbuddywatering).isBefore(time.toZDT())) {
rules.runRule("growbuddy-watering");
}
},
tags: [],
});
rules.JSRule({
name: "Growbuddy - Toggle Grow Phase",
description: "change lightstop acording to growphase for growbuddy",
triggers: [
triggers.ItemStateChangeTrigger("GrowbuddyGrowPhase"),
triggers.ItemStateChangeTrigger("GrowbuddyLightStart"),
],
execute: (data) => {
const phasestop = time
.toZDT(lightstart.state)
.plus(time.Duration.ofHours(parseInt(growphase.state)));
lightstop.postUpdate(phasestop);
},
tags: [],
});
rules.JSRule({
name: "Growbuddy - Update LightStart",
description: "update lightstart according to daychange",
triggers: [triggers.GenericCronTrigger("0 0 0 * * ? *")],
execute: (data) => {
const newlightstart = time
.toZDT(lightstart)
.plus(time.Duration.ofHours(24));
lightstart.sendCommand(newlightstart);
},
tags: [],
});
The Items
You need to create the following items with the exact names and item-types:
- SmartplugGrowlight (itemType: Switch)
- SmartplugVentilation (itemType: Switch)
- SmartplugCirculation (itemType: Switch)
- SmartplugWatering (itemType: Switch)
- GrowbuddyMainSwitch (itemType: Switch)
- GrowbuddyGrowPhase (itemType: Number:Time)
- GrowbuddyWaterInterval (itemType: Number:Time)
- GrowbuddyWaterDuration (itemType: Number:Time)
- GrowbuddyWatering (itemType: DateTime)
- GrowbuddyLightStart (itemType: DateTime)
- GrowbuddyLightStop (itemType: DateTime)
If you’re too lazy to add this items by hand, you can import the growbuddy.items to your MainUI or textual config. To import to Main UI navigate to the items section click on the + sign to add an item and choose “Add Item from Textual Definition” and paste the following code.
Switch SmartplugGrowlight "Grow Light" <switch>
Switch SmartplugVentilation "Air Ventilation" <switch>
Switch SmartplugCirculation "Air Circulation" <switch>
Switch SmartplugWatering "Watering Pump" <switch>
Switch GrowbuddyMainSwitch "Main Switch" <switch>
Number:Time GrowbuddyGrowPhase "Grow Phase Selector" <time>
Number:Time GrowbuddyWaterInterval "Watering Interval" <time>
Number:Time GrowbuddyWaterDuration "Watering Duration" <time>
DateTime GrowbuddyWatering "Watering Time" <time>
DateTime GrowbuddyLightStart "Grow Light Start Time" <time>
DateTime GrowbuddyLightStop "Grow Light Stop Time" <time>
Script to initialize DateTime Items (you only need that once)
add this script in MainUI in the scripts section (choose ECMA2021) and execute it.
let now = time.ZonedDateTime.now();
items.getItem("GrowbuddyLightStart").sendCommand(now.minus(time.Duration.ofHours(4)));
items.getItem("GrowbuddyLightStop").sendCommand(now.plus(time.Duration.ofHours(8)));
items.getItem("GrowbuddyWatering").sendCommand(now.plus(time.Duration.ofMinutes(1)));
Screenshots
Changelog
Version 1.0
- initial release (stable version)
Resources
uid: GrowBuddy
tags: []
props:
parameters:
- defaultValue: 24h
description: Time format (12h|24h)
label: Format
name: timeFormat
required: true
type: TEXT
- context: item
defaultValue: GrowbuddyMainSwitch
description: Item to control
label: Main Switch Item
name: mainswitch
required: false
type: TEXT
- context: item
defaultValue: GrowbuddyLightStart
description: Item to control
label: Starttime Item
name: startitem
required: false
type: TEXT
- context: item
defaultValue: GrowbuddyLightStop
description: Item to control
label: Stoptime Item
name: stopitem
required: false
type: TEXT
- context: item
defaultValue: SmartplugGrowlight
description: Item to control
label: MainLight Item
name: light
required: false
type: TEXT
- context: item
defaultValue: GrowbuddyGrowPhase
description: Item to control
label: GrowPhase Item
name: growphase
required: false
type: TEXT
- context: item
defaultValue: GrowbuddyWaterInterval
description: Item to control
label: Watering Interval Item
name: interval
required: false
type: TEXT
- context: item
defaultValue: GrowbuddyWaterDuration
description: Item to control
label: Watering Duration Item
name: duration
required: false
type: TEXT
- context: item
defaultValue: GrowbuddyWatering
description: Item to control
label: Watering Toggle Item
name: toggle
required: false
type: TEXT
- context: item
defaultValue: SmartplugWatering
description: Item to control
label: Watering Item
name: watering
required: false
type: TEXT
- context: item
defaultValue: SmartplugCirculation
description: Item to control
label: Circular Air Item
name: circulation
required: false
type: TEXT
- context: item
defaultValue: SmartplugVentilation
description: Item to control
label: Air Ventilation Item
name: ventilation
required: false
type: TEXT
parameterGroups: []
timestamp: Aug 30, 2023, 3:03:51 PM
component: f7-block
config:
label: GrowBuddy
style:
max-width: 690px
slots:
default:
- component: f7-row
config:
class:
- margin
slots:
default:
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-block
config:
title: GrowBuddy
slots: {}
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-knob-cell
config:
action: toggle
actionCommand: ON
actionCommandAlt: OFF
actionItem: =(props.mainswitch)
actionOptions: ON,OFF
color: "=(items[props.mainswitch].state === 'ON') ? 'green' : 'red'"
commandInterval: 500
expandable: false
footer: "=(items[props.mainswitch].state === 'ON') ? 'ACTIVE' : 'DISABLED'"
icon: "=(items[props.mainswitch].state === 'ON') ? 'iconify:mingcute:power-fill' : 'iconify:mingcute:power-line'"
iconColor: "=(items[props.mainswitch].state === 'ON') ? 'green' : 'red'"
releaseOnly: true
responsive: true
style:
text-shadow: 1px 1px black
title: Main Switch
- component: f7-row
config:
class:
- margin
slots:
default:
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-knob-cell
config:
action: toggle
actionCommand: 18 h
actionCommandAlt: 12 h
actionItem: =(props.growphase)
actionOptions: 12,18
color: "=(items[props.light].state === 'ON') ? 'green' : 'red'"
commandInterval: 500
expandable: false
footer: "=(items[props.growphase].state === '12 h') ? 'FLOWERING (12 HOURS)' : 'GROW (18 HOURS)'"
icon: "=(items[props.growphase].state === '12 h') ? 'iconify:ph:plant' : 'iconify:ph:plant-duotone'"
iconColor: "=(items[props.growphase].state === '12 h') ? 'orange' : 'green'"
releaseOnly: true
responsive: true
style:
text-shadow: 1px 1px black
title: Grow Phase
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-block
config:
title: Start
slots: {}
- component: f7-row
config:
class:
- margin
slots:
default:
- component: f7-col
config:
width: "100"
slots:
default:
- component: f7-row
config: {}
slots:
default:
- component: f7-col
config:
style:
text-align: center
width: "45"
slots:
default:
- component: oh-button
config:
action: command
actionCommand: =(dayjs(items[props.startitem].state).add(1, 'hour').format('YYYY-MM-DDTHH:mm:ss.ZZ'))
actionItem: =(props.startitem)
style:
--f7-button-text-color: var(--f7-text-color)
font-family: u2400
font-size: 150%
text: ▲
- component: f7-col
config:
width: "10"
- component: f7-col
config:
style:
text-align: center
width: "45"
slots:
default:
- component: oh-button
config:
action: command
actionCommand: =(dayjs(items[props.startitem].state).add(1, 'minute').format('YYYY-MM-DDTHH:mm:ss.ZZ'))
actionItem: =(props.startitem)
style:
--f7-button-text-color: var(--f7-text-color)
font-family: u2400
font-size: 150%
text: ▲
- component: f7-row
config: {}
slots:
default:
- component: f7-col
config:
width: "45"
slots:
default:
- component: oh-label-card
config:
label: "=(((items[props.startitem].state === 'NULL') || !props.startitem) ? '?': props.timeFormat == '12h' ? dayjs(items[props.startitem].state).format('hh'): dayjs(items[props.startitem].state).format('HH'))"
- component: f7-col
config:
style:
margin: auto
text-align: center
width: "10"
slots:
default:
- component: Label
config:
style:
font-size: 200%
text: ":"
- component: f7-col
config:
width: "45"
slots:
default:
- component: oh-label-card
config:
label: "=(((items[props.startitem].state === 'NULL') || !props.startitem) ? '?': dayjs(items[props.startitem].state).format('mm'))"
- component: f7-row
config: {}
slots:
default:
- component: f7-col
config:
style:
text-align: center
width: "45"
slots:
default:
- component: oh-button
config:
action: command
actionCommand: =(dayjs(items[props.startitem].state).add(-1, 'hour').format('YYYY-MM-DDTHH:mm:ss.ZZ'))
actionItem: =(props.startitem)
style:
--f7-button-text-color: var(--f7-text-color)
font-family: u2400
font-size: 150%
text: ▼
- component: f7-col
config:
width: "10"
- component: f7-col
config:
style:
text-align: center
width: "45"
slots:
default:
- component: oh-button
config:
action: command
actionCommand: =(dayjs(items[props.startitem].state).add(-1, 'minute').format('YYYY-MM-DDTHH:mm:ss.ZZ'))
actionItem: =(props.startitem)
style:
--f7-button-text-color: var(--f7-text-color)
font-family: u2400
font-size: 150%
text: ▼
- component: f7-row
config:
class:
- margin
slots:
default:
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-block
config:
title: Stop
slots: {}
- component: f7-row
config:
class:
- margin
slots:
default:
- component: f7-col
config:
width: "=(props.timeFormat === '12h') ? '70': '100'"
slots:
default:
- component: f7-row
config: {}
slots:
default:
- component: f7-col
config:
width: "45"
slots:
default:
- component: oh-label-card
config:
label: "=(((items[props.stopitem].state === 'NULL') || !props.stopitem) ? '?': props.timeFormat == '12h' ? dayjs(items[props.stopitem].state).format('hh'): dayjs(items[props.stopitem].state).format('HH'))"
- component: f7-col
config:
style:
margin: auto
text-align: center
width: "10"
slots:
default:
- component: Label
config:
style:
font-size: 200%
text: ":"
- component: f7-col
config:
width: "45"
slots:
default:
- component: oh-label-card
config:
label: "=(((items[props.stopitem].state === 'NULL') || !props.stopitem) ? '?': dayjs(items[props.stopitem].state).format('mm'))"
- component: f7-row
config:
class:
- margin
slots:
default:
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-knob-cell
config:
action: toggle
actionCommand: ON
actionCommandAlt: OFF
actionItem: =(props.light)
commandInterval: 500
expandable: false
footer: "=(items[props.light].state === 'ON') ? 'RUNNING' : 'OFF'"
icon: "=(items[props.light].state === 'ON') ? 'iconify:tabler:bulb-filled' : 'iconify:tabler:bulb-off'"
iconColor: "=(items[props.light].state === 'ON') ? 'yellow' : 'gray'"
primaryColor: "#663311"
releaseOnly: true
responsive: true
secondaryColor: "#114455"
style:
background-color: rgba(100, 100, 100, 0.3)
text-shadow: 1px 1px black
title: Grow Light
- component: f7-col
config:
width: "100"
slots:
defaultValue:
- component: oh-knob-cell
config:
action: toggle
actionCommand: ON
actionCommandAlt: OFF
actionItem: =(props.ventilation)
commandInterval: 500
expandable: false
footer: "=(items[props.ventilation].state === 'ON') ? 'RUNNING' : 'OFF'"
icon: "=(items[props.ventilation].state === 'ON') ? 'iconify:mdi:ventilation' : 'iconify:mdi:ventilation-off'"
iconColor: "=(items[props.ventilation].state === 'ON') ? 'green' : 'gray'"
primaryColor: "#663311"
releaseOnly: true
responsive: true
secondaryColor: "#114455"
style:
background-color: rgba(100, 100, 100, 0.3)
text-shadow: 1px 1px black
title: Air Ventilation
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-knob-cell
config:
action: toggle
actionCommand: ON
actionCommandAlt: OFF
actionItem: =(props.circulation)
commandInterval: 500
expandable: false
footer: "=(items[props.circulation].state === 'ON') ? 'RUNNING' : 'OFF'"
icon: "=(items[props.circulation].state === 'ON') ? 'iconify:material-symbols:mode-fan' : 'iconify:material-symbols:mode-fan-off-outline-rounded'"
iconColor: "=(items[props.circulation].state === 'ON') ? 'green' : 'gray'"
primaryColor: "#663311"
releaseOnly: true
responsive: true
secondaryColor: "#114455"
style:
background-color: rgba(100, 100, 100, 0.3)
text-shadow: 1px 1px black
title: Air Circulation
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-knob-cell
config:
action: options
actionItem: =(props.interval)
actionOptions: 2 min,5 min,10 min,15 min,20 min,30 min,1 h,2 h,4 h,6 h,12 h,24 h
commandInterval: 500
expandable: false
footer: =(items[props.interval].state)
icon: "=(items[props.watering].state === 'ON') ? 'iconify:mdi:watering-can' : 'iconify:mdi:watering-can-outline'"
iconColor: "=(items[props.watering].state === 'ON') ? 'green' : 'gray'"
primaryColor: "#663311"
releaseOnly: true
responsive: true
secondaryColor: "#114455"
style:
background-color: rgba(100, 100, 100, 0.3)
text-shadow: 1px 1px black
title: Watering Interval
- component: f7-col
config:
width: "100"
slots:
default:
- component: oh-knob-cell
config:
action: options
actionItem: =(props.duration)
actionOptions: 5 s,10 s,15 s,20 s,30 s,60 s,90 s
commandInterval: 500
expandable: false
footer: =(items[props.duration].state)
icon: "=(items[props.watering].state === 'ON') ? 'iconify:game-icons:duration' : 'iconify:game-icons:duration'"
iconColor: "=(items[props.watering].state === 'ON') ? 'green' : 'gray'"
primaryColor: "#663311"
releaseOnly: true
responsive: true
secondaryColor: "#114455"
style:
background-color: rgba(100, 100, 100, 0.3)
text-shadow: 1px 1px black
title: Watering Duration
I hope this installation tutorial is complete… otherwise feel free to ask ;)…