GrowBuddy v1.0 - Grow Your Plants Indoor

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 ;)…

3 Likes

Hoy! This is fantastic! I’ve been looking to hydroponic systems for a while now and this hits the mark on the automation side :slight_smile:
Will you add anything for the temperature and humidity and to measure light? Or the TDS in water? What kind of growing system are you using with this?

Hi Pedro,

This widget was coded for a hydroponic system… but suits on any other system
that uses lamp, fan, ventilation and waterpump!
However my first post on Marketplace was corrupted and I had to remove and readd it. Right now is it not published to the Marketplace but you can install it by hand from this thread :wink:

For now you can only take it as is, and atm I’m not planning to develop any additional functions…
imo there’s no advantage in adding temp&humid support as long as there is no function to adjust the fan and ventilation speed - but feel free to add some new functions…

cheers
Dan

1 Like

See edit history if there is interest.

1 Like

See edit history if there is interest.

see edit history if there is interest

1 Like

See edit history if there is interest.

see edit history if there is interest

See edit history if there is interest.

see edit history if there is interest

See edit history if there is interest.

see edit history if there is interest

See edit history if there is interest.

See edit history if there is interest.

see edit history if there is interest

See edit history if there is interest.

see edit history if there is interest

See edit history if there is interest.

see edit history if there is interest

Fine! looks promising, I’ll add this to my new test rules file but I think I will test it tomorrow morning, right now I have to take care of myself and eat something… it’s near to 22h here in Germany… thanks and I’ll get back to you tomorrow… when I remember this right you’re 8 hours off in Colorado so have a nice afternoon :wink:

1 Like