Automated Grow Light

Hello everyone

I am new to programming and would like to ask for your help with my project. I plan to make an automated grow light for my aquaponics, which automatically turns on in the morning and off at night. But I am having difficulties with the code because it only works for a short period.

I used this grow light - Full Spectrum 290 LED indoor plant Grow light bulbs E27 lamp veg cultivo growth Green house Hydro sunlight Phyto Lamp Flower K | Lazada PH, which I connected to a Relay and into the **Arduino Uno. **

Here is a copy of my code:

int relayPin = 8; 
int relayStateON = HIGH; 
int relayStateOFF; 

void setup() { 
Serial.begin(9600); 
pinMode(relayPin,OUTPUT); 
} 

void loop() { 
int RelayState; 
while (RelayState=relayStateON){ 
 digitalWrite(relayPin,relayStateOFF); 
 delay(43200000); 
 digitalWrite(relayPin,relayStateON); 
 delay(43200000); 
} 
}

Thank you!

Here is my grow light code that works very well. Perhaps it will give a few hints.
I use the internal Real Time Clock of the ESP32. I set the clock at boot and check the time once in a while to see if the grow light status needs to change.

#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "time.h"
//
//
hw_timer_t * timer = NULL;
EventGroupHandle_t eg;
#define evtDoDisplay ( 1 << 1 )
#define evtParseMQTT ( 1 << 5 )
#define evtDoTheSunLampThing ( 1 << 10 )
//
int SunLampManual = 0;
int SunLampAuto = 0;
////
WiFiClient wifiClient;
PubSubClient MQTTclient( mqtt_server, mqtt_port, wifiClient );
////
String str_eTopic;
char strPayload [300] = {'\0'};
////
byte mac[6];
////
SemaphoreHandle_t sema_MQTT_Parser;;
SemaphoreHandle_t sema_DoTheSunLampThing;
SemaphoreHandle_t sema_MQTT_KeepAlive;
////
volatile int iDoTheThing = 0;
////
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  iDoTheThing++;
  if ( iDoTheThing == 60000 )
  {
    xEventGroupSetBitsFromISR( eg, evtDoTheSunLampThing, &xHigherPriorityTaskWoken );
    iDoTheThing = 0;
  }
}
////
void setup()
{
  //
  gpio_config_t io_cfg = {};
  io_cfg.mode = GPIO_MODE_OUTPUT;
  //bit mask of the pins to set
  io_cfg.pin_bit_mask = ( (1ULL << GPIO_NUM_15) );
  //configure GPIO with the given settings
  gpio_config(&io_cfg);
  REG_WRITE(GPIO_OUT_W1TC_REG, BIT15); // sunlamp
  //
  /* Use 4th timer of 4.
    1 tick 1/(80MHZ/80) = 1us set divider 80 and count up.
    Attach onTimer function to timer
    Set alarm to call timer ISR, every 1000uS and repeat / reset ISR (true) after each alarm
    Start an timer alarm
  */
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 1000, true);
  timerAlarmEnable(timer);
  //
  for ( int i = 0; i < (300 - 1); i++ )
  {
    strPayload[i] = '\0';
  }
  str_eTopic.reserve(300);
  //
  eg = xEventGroupCreate();
  //
  sema_MQTT_Parser = xSemaphoreCreateBinary();
  sema_DoTheSunLampThing = xSemaphoreCreateBinary();
  sema_MQTT_KeepAlive = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_DoTheSunLampThing );
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  ////
  ////
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 7000, NULL, 5, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 7000, NULL, 2, NULL, 1 ); //this task makes a WiFi and MQTT connection.
  xTaskCreatePinnedToCore( fDoTheSunLampThing, "fDoTheSunLampThing", 2000, NULL, 3, NULL, 1 );
  ////
} //setup() END
////
////
////
////
void GetTheTime()
{
  char* ntpServer = "2.us.pool.ntp.org";
  int gmtOffset_sec = -(3600 * 7 );
  int daylightOffset_sec = 3600;
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
////
// http://www.cplusplus.com/reference/ctime/strftime/
////
int getHour()
{
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  char _time[ 5 ];
  strftime( _time, 80, "%T", &timeinfo );
  return String(_time).toInt();
}
////
void printLocalTime()
{
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  char _time[ 80 ];
  strftime( _time, 80, "%T", &timeinfo );
  log_i( "%s", _time);
}
////
void fDoTheSunLampThing( void * parameter )
{
  // int x = gpio_get_level( GPIO_NUM_15 ); // reads gpio pin state returns an int
  // SunLampManual = sunlamp on manual mode, automatic mode off for manual mode to work
  // SunLampAuto = sun lamp enable automatic mode
  bool AlreadyOn = false;
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoTheSunLampThing, pdTRUE, pdTRUE, portMAX_DELAY );
    vTaskDelay( 6 );
    xSemaphoreTake( sema_DoTheSunLampThing, portMAX_DELAY );
    // sun lamp on/off auto disabled
    if ( (SunLampManual == 1) && (SunLampAuto == 0)  )
    {
      if ( !AlreadyOn )
      {
        REG_WRITE(GPIO_OUT_W1TS_REG, BIT15);
        AlreadyOn = true;
      } else {
        REG_WRITE(GPIO_OUT_W1TC_REG, BIT15);
        AlreadyOn = false;
      }
    } else if ( (SunLampManual == 0) && (SunLampAuto == 1) ) // light off auto enabled
    {
      int _hour = getHour();
      if ( (_hour >= 7) && (_hour <= 17) )
      {
        REG_WRITE(GPIO_OUT_W1TS_REG, BIT15);
        AlreadyOn = true;
      }
      if ( (_hour < 7) || (_hour > 16) )
      {
        REG_WRITE(GPIO_OUT_W1TC_REG, BIT15);
        AlreadyOn = false;
      }
    } else {
      REG_WRITE(GPIO_OUT_W1TC_REG, BIT15);
      AlreadyOn = false;
    }
    xSemaphoreGive( sema_DoTheSunLampThing );
    //log_i( "fDoTheSunLampThing high watermark %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
} // void fDoTheSunLampThing( void * parameter )
////
/*
    Important to not set vtaskDelay to less then 10. Errors begin to develop with the MQTT and network connection.
    makes the initial wifi/mqtt connection and works to keeps those connections open.
*/
void MQTTkeepalive( void *pvParameters )
{
  // setting must be set before a mqtt connection is made
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds
  for (;;)
  {
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); //
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      log_i( "MQTT keep alive found MQTT status %s WiFi status %s", String(wifiClient.connected()), String(WiFi.status()) );
      if ( !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    vTaskDelay( 250 );
  }
  vTaskDelete ( NULL );
}
////
void connectToMQTT()
{
  log_i( "connect to mqtt" );
  // create client ID from mac address
  String clientID = String(mac[0]) + String(mac[5]) ;
  log_i( "connect to mqtt clientID %s", clientID );
  while ( !MQTTclient.connected() )
  {
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password );
    log_i( "connecting to MQTT" );
    vTaskDelay( 250 );
  }
  log_i("MQTT Connected");
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( mqtt_topic );
}
//
void connectToWiFi()
{
  log_i( "connect to wifi" );
  while ( WiFi.status() != WL_CONNECTED )
  {
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    log_i(" waiting on wifi connection" );
    vTaskDelay( 4000 );
  }
  log_i( "Connected to WiFi" );
  WiFi.macAddress(mac);
  log_i( "mac address %d.%d.%d.%d.%d", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]  );
  WiFi.onEvent( WiFiEvent );
  GetTheTime();
  printLocalTime();
}
////
void fparseMQTT( void *pvParameters )
{
  xSemaphoreGive ( sema_MQTT_Parser );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtParseMQTT, pdTRUE, pdTRUE, portMAX_DELAY ); //
    xSemaphoreTake( sema_MQTT_Parser, portMAX_DELAY );
    if ( String(str_eTopic) == topic_SunLampOn )
    {
      xSemaphoreTake( sema_DoTheSunLampThing, portMAX_DELAY );
      SunLampManual = String(strPayload).toInt();
      xSemaphoreGive( sema_DoTheSunLampThing );
    }
    if ( String(str_eTopic) == topic_SunLampEnable )
    {
      xSemaphoreTake( sema_DoTheSunLampThing, portMAX_DELAY );
      SunLampAuto = String(strPayload).toInt();
      xSemaphoreGive( sema_DoTheSunLampThing );
    }
    // clear pointer locations
    for ( int i = 0; i < (300 - 1); i++ )
    {
      strPayload[i] = '\0';
    }
    str_eTopic = ""; //clear string buffer
    xSemaphoreGive( sema_MQTT_Parser );
  }
} // void fparseMQTT( void *pvParameters )
////
// Important to get as much code out of the callback for realible operations.
////
static void mqttCallback(char* topic, byte * payload, unsigned int length)
{
  xSemaphoreTake( sema_MQTT_Parser, portMAX_DELAY);
  str_eTopic = topic + '\0';
  int i = 0;
  for ( i; i < length; i++) {
    strPayload[i] = ((char)payload[i]);
  }
  strPayload[i] = '\0';
  //log_i( "topic %s payload %s" ,str_eTopicPtr, strPayloadPtr );
  xSemaphoreGive ( sema_MQTT_Parser );
  xEventGroupSetBits( eg, evtParseMQTT ); // trigger tasks
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
// great trouble shooting tool when uncommented
////
void WiFiEvent(WiFiEvent_t event)
{
  // log_i( "[WiFi-event] event: %d\n", event );
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      log_i("Connected to access point");
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
    default: break;
  }
}
////
void loop() { }

babyashee:
I am having difficulties with the code because it only works for a short period.

What happens? Does the grow light come on? For how long?

I'd suggest that a Real Time Clock module would be helpful - they're cheap & easy to use.

while (RelayState=relayStateON){

no, it's

while (RelayState == relayStateON) {

if you wanted to compare them for equality. But you also have the problem that 'RelayState' is declared inside loop(), so it doesn't retain it's value for each iteration of loop(), it gets a new value each time.

Actually, no attempt has been made (except in the flawed comparison of the if statement) to assign any legitimate value to 'RelayState' so it will always have a random value.

This does the same as your sketch but designed to be more readble:

const byte RelayPin = 8;
const int RelayStateON = HIGH;
const int RelayStateOFF = LOW;


const unsigned long SECOND = 1000;
const unsigned long MINUTE = 60 * SECOND;
const unsigned long HOUR = 60 * MINUTE;


void setup()
{
  Serial.begin(9600);
  pinMode(RelayPin, OUTPUT);
}


void loop()
{
    digitalWrite(RelayPin, RelayStateOFF);
    delay(12 * HOUR);
    digitalWrite(RelayPin, RelayStateON);
    delay(12 * HOUR);
}

(Note: Many relay modules use LOW for ON and HIGH for OFF)

aarg:
But you also have the problem that 'RelayState' is declared inside loop(), so it doesn't retain it's value for each iteration of loop(), it gets a new value each time.

That does not sound correct at all!

That may be true for a function called from your other code, but loop() is not such a function, it is only ever called once and never exits.

Paul__B:
That may be true for a function called from your other code, but loop() is not such a function, it is only ever called once and never exits.

That is incorrect. The loop() function is a function like any other. It is called repeatedly inside an infinite loop. Any (non-static) local variables are created fresh each time loop() starts.

This particular loop() never exits because the 'while' loop it contains never ends. But even if it did end it would not matter that RelayState is created fresh because the only thing done to it is to assign it a value: (RelayState=relayStateON). The value ('relayStateON') is a constant 1 so RelayState is always set to 1. Since 1 is not zero it is treated as 'true' for the purpose of the 'while' loop.

int relayStateON = HIGH;
void loop() {
  int RelayState;
  while (RelayState=relayStateON) {
  }
}

This is exactly equivalent to:

void loop() {
  while (true) {
  }
}

Paul__B:
That does not sound correct at all!

That may be true for a function called from your other code, but loop() is not such a function, it is only ever called once and never exits.

Basically main is built

setup();

while(1)
{
loop();
}

As you can see loop() is called many times.

This particular loop() never exits because the 'while' loop it contains never ends. But even if it did end it would not matter that RelayState is created fresh because the only thing done to it is to assign it a value: (RelayState=relayStateON). The value ('relayStateON') is a constant 1 so RelayState is always set to 1. Since 1 is not zero it is treated as 'true' for the purpose of the 'while' loop.

Yes. But that assignment was made in error, it was intended to be a comparison. Hence to be used as the OP probably intended, 'RelayState' would have to be declared as a global variable, or as a static variable inside loop(), and be assigned a meaningful value there.

It is common to see "used once" variables in loop, e.g. 'int reading = analogRead(A2)' or 'unsigned long currentMillis = millis()'.