Functions? Creating a Radio Hub

All of my previous posts on this forum have been my Automatic Chicken Coop Door project. This is a continuation of that project. Essentially I wanted to have the chicken coop send a message to a hub in the house, telling the hub if it's closed or open. Originally I used a led that would turn on and off and that worked fine, but I decided to do something a little fancier.

For this project I want to display the Chicken Coop Door's state on an LCD, but I also want to create a security system. I have the Arduino Uno attached to a keypad (1,2,3,4,5,6,7,8.9.0,#,*), 2x16 LCD, and a nRF module. Right now I'm struggling to create a menu or some type of layout for the LCD. I want to have different screens. For the time being I'd like to focus on displaying the signal I'm getting from the Chicken Coop, since my motion senor is en route.

So on screen 1 I'd like to have a menu that allows you to go to either the security system's screen or the chicken coop door's screen. ( press * for Coop, and press # for security). After you press * we would go to screen 2, where I'd like to display "the door is" on the top line of the LCD and on the bottom line I'd like to display the Door's state, open/closed.

I don't know how to make the door's state dynamic. I tried to create a function that would spew out a result and include that under screen 2. The problem I'm having is that when I click * I only get to the screen 2 if the Coop Door's Arduino isn't transmitting. When I turn off the chicken coop door and click * I can get to screen 2 and it displays the coop's door as being open. So I have no idea how to display the coop's state live.

Thanks for the Help!

The Keypad pins-

Row pins = 3, 5, 6, 7

Column pins = 8, 9, 10

LCD pins-
A0 - RS
A1 - E
A2 - D4
A3 - D5
A4 - D6
A5 - D7

The rest of the pins are attached to the the 5v pins and ground as necessary.

nRF Pins-
13 - SCK
12 - MISC
11 - MOSI
2 - CE
4 - CS

#include <Keypad.h>
#include <LiquidCrystal.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>


boolean doorState = LOW;

//Radio
int msg[1];
RF24 radio(2, 4);
const uint64_t pipes[2] = {
  0xF0F0F0F000LL, 0xF0F0F0F0FFLL
};

//LCD
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

//Keypad
const byte ROWS = 4;
const byte COLS = 3;

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[ROWS] = {3, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10};

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

void setup() {
  
  radio.begin();
  radio.setDataRate(RF24_250KBPS);
  radio.setChannel(100);
  radio.setRetries(15, 15);
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1, pipes[1]);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  //Screen 1
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("* for Coop");
  lcd.setCursor(0, 1);
  lcd.print("# for Security");

  Serial.begin(9600);
}

void loop() {
  char customKey = customKeypad.getKey();

  if (customKey) {
    Serial.println(customKey);
  }

  //Radio Code
  if (radio.available()) {
    bool done = false;
    while (!done) {
      done = radio.read(msg, 1);

      if (msg[0] == 111) {
        delay(10);
        doorState = HIGH;
      }

      else if (msg[0] == 112) {
        doorState = HIGH;
      }
    }
  }

  // Screen 2
  if (customKey == '*' ) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" Door Is ");
    CoopDoor();

  }
}

void CoopDoor() {
  if (doorState = HIGH) {
    lcd.setCursor(0, 1);
    lcd.print("Open");
    delay(1000);
  }
  else if (doorState = LOW) {
    lcd.setCursor(0, 1);
    lcd.print("Closed");
    delay(1000);
  }
}

Start with the low hanging fruit...

...
  if (doorState = HIGH) {
...
  else if (doorState = LOW) {
...

I'm sorry. I don't understand what you mean. Does that mean that the function is written correctly or did i implement it wrong?

= for assignment
== for comparison

Oh! Ok, thank you! I'll fix that as soon as I get home. Thanks for the help!

Hi, that worked beautifully! The only way I can get the door's state to update is if I click the rest button. Is there some way I can get the door's state to update automatically on screen 2?

Post your code as it is now

There you go! The only thing I changed was how many equal signs I had in the CoopDoor function.

#include <Keypad.h>
#include <LiquidCrystal.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>


boolean doorState = LOW;

//Radio
int msg[1];
RF24 radio(2, 4);
const uint64_t pipes[2] = {
  0xF0F0F0F000LL, 0xF0F0F0F0FFLL
};

//LCD
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

//Keypad
const byte ROWS = 4;
const byte COLS = 3;

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[ROWS] = {3, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10};

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

void setup() {
  radio.begin();
  radio.setDataRate(RF24_250KBPS);
  radio.setChannel(100);
  radio.setRetries(15, 15);
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1, pipes[1]);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  //Screen 1
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("* for Coop");
  lcd.setCursor(0, 1);
  lcd.print("# for Security");

  Serial.begin(9600);
}

void loop() {
  char customKey = customKeypad.getKey();

  if (customKey) {
    Serial.println(customKey);
  }

  //Radio Code
  if (radio.available()) {
    bool done = false;
    while (!done) {
      done = radio.read(msg, 1);

      if (msg[0] == 111) {
        delay(10);
        doorState = HIGH;
      }

      else if (msg[0] == 112) {
        doorState = HIGH;
      }
    }
  }

  // Screen 2
  if (customKey == '*' ) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    CoopDoor();
  }
}

void CoopDoor() {
  if (doorState == HIGH) {
    lcd.setCursor(0, 1);
    lcd.print("Open");
    delay(1000);
  }
  else if (doorState == LOW) {
    lcd.setCursor(0, 1);
    lcd.print("Closed");
    delay(1000);
  }
}

If you want the door state to update on the screen in real time then you need to write to it periodically, perhaps by calling the CoopDoor() function but only when screen 2 is being displayed.

Suggestions:

Add a variable that holds an indication of the screen currently on display
At the end of loop() use millis() to determine whether the required screen refresh period has passed since the previous iteration of loop() and, if screen 2 is currently being displayed call the CoopDoor function to update the screen.

If you are not familiar with using millis() for timing then read Using millis() for timing. A beginners guide, Several things at the same time and look at the BlinkWithoutDelay example in the IDE.

Ok the way I understand it is under my screen 2 if statement I should have a variable that tells me that screen 2 is displaying.

So

  if (customKey == '*' ) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    ScreenTwo=HIGH;
  }
}

Then have an other if statement

  if (ScreenTwo == HIGH){
     CoopDoor();
     delay(1000);
}

I think I used the delay correctly here. I want to check the CoopDoor function every second and display that change.

I think I used the delay correctly here. I want to check the CoopDoor function every second and display that change.

The idea sounds OK but personally I would use millis() for non blocking timing as I described in my previous post but try it with delay() first.

Right! I'm just not as familiar with millis(), so until I can mess around with it I'm going to use delay it's place. Thanks though for that really in depth forum post you gave me the link to. That really helped!

I'm confused by the whole millis thing. I understand why it's better than delay in this instance, but I don't know how to implement it.

I tried doing this, but It would switch over to the next screen and back to the 1st before I could react, so something is definitely wrong with the millis function.

#include <Keypad.h>
#include <LiquidCrystal.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>

//Timer
unsigned long startMillis;
unsigned long currentMillis;
const unsigned long ScreenRefresh = 5000;


boolean ScreenTwo = LOW;
boolean doorState = LOW;

//Radio
int msg[1];
RF24 radio(2, 4);
const uint64_t pipes[2] = {
  0xF0F0F0F000LL, 0xF0F0F0F0FFLL
};

//LCD
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

//Keypad
const byte ROWS = 4;
const byte COLS = 3;

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[ROWS] = {3, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10};

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

void setup() {
  radio.begin();
  radio.setDataRate(RF24_250KBPS);
  radio.setChannel(100);
  radio.setRetries(15, 15);
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1, pipes[1]);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  //Timer
  startMillis = millis();

  //Screen 1
  lcd.begin(16, 2);
  MainMenu();

  Serial.begin(9600);
}

void loop() {
  //Timing
  currentMillis = millis();

  //Keypad
  char customKey = customKeypad.getKey();
  if (customKey) {
    Serial.println(customKey);
  }

  //Radio Code
  if (radio.available()) {
    bool done = false;
    while (!done) {
      done = radio.read(msg, 1);

      if (msg[0] == 111) {
        delay(10);
        doorState = HIGH;
      }

      else if (msg[0] == 112) {
        doorState = HIGH;
      }
    }
  }

  // Screen 2
  if (customKey == '*' ) {
    currentMillis = millis();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    ScreenTwo = HIGH;
    
    if (startMillis - currentMillis >= ScreenRefresh) {
      ScreenTwo = LOW;
      lcd.clear();
      MainMenu();
    }
  }

  if (ScreenTwo == HIGH) {
    CoopDoor();
  }
}

void CoopDoor() {
  if (doorState == HIGH) {
    lcd.setCursor(0, 1);
    lcd.print("Open");

  }
  else if (doorState == LOW) {
    lcd.setCursor(0, 1);
    lcd.print("Closed");

  }
}

void MainMenu () {
  lcd.setCursor(0, 0);
  lcd.print("* for Coop");
  lcd.setCursor(0, 1);
  lcd.print("# for Security");
}

Just FYI I also decide to make screen 1 a function called MainMenu, so I could go back to the start without having to press reset.

I have not had time to look at your code in detail but this line stood out

   if (startMillis - currentMillis >= ScreenRefresh)

If the variable names reflect the data they hold then the logic is wrong.

When using millis() for timing you need to check whether a certain period of time has passed since something occurred. This is best done by subtracting the start time from the current time and comparing it with the required timing period. You seem to have done this the wrong way round based on the variable names.

Replacing delay() with millis() timing in your code to refresh screen 2 when it is being displayed should be simple. When you first move to screen 2 set ScreenTwo HIGH and save the start time and call CoopDoor(). Then, independent on any other code do this in loop()

if (ScreenTwo == HIGH)  //if we are showing screen 2
{
  if (currentMillis - startMillis >= ScreenRefresh)  //if the period has passed
  {
    ShowCoop();  //refresh the screen
    startMillis() = currentMillis();  //save the time that it was refreshed
  } 
}

I tried to simplify my code. I think I need to do a few things. Maybe make the ScreenTwo= LOW and lcd.clear() stuff a function to clean up everything, or put the millis stuff into the ~~ I feel like something should go here ~~ part.

Helping a novice like me, probably seems grueling, but I really appreciate all your help!

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long ScreenRefresh = 5000;

void loop(){

  currentMillis = millis();


  if (customKey == '*' ) {
    currentMillis = millis();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    ScreenTwo = HIGH;
 
~~Feel Like Something Should Go Here ~~
    
    ScreenTwo = LOW;
    lcd.clear();
    MainMenu();
    }
  }
 
 if (ScreenTwo == HIGH)  //if we are showing screen 2
  {
    if (currentMillis - startMillis >= ScreenRefresh)  //if the period has passed
    {
      CoopDoor();  //refresh the screen
      startMillis = currentMillis;  //save the time that it was refreshed
    }
  }
}

So maybe something like this?

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long ScreenRefresh = 5000;

void loop(){

  currentMillis = millis();


  if (customKey == '*' ) {
    currentMillis = millis();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    ScreenTwo = HIGH;
  
    }
  }
 
 if (ScreenTwo == HIGH)  //if we are showing screen 2
  {
    if (currentMillis - startMillis >= ScreenRefresh)  //if the period has passed
    {
      CoopDoor();  //refresh the screen
      startMillis = currentMillis;  //save the time that it was refreshed
      ClearScreen();
    }
  }
}

void ClearScreen (){

    ScreenTwo = LOW;
    lcd.clear();
    MainMenu();

You have messed up the last code that you posted by ending the loop() function too early,

Here it is Auto Formatted

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long ScreenRefresh = 5000;

void loop()
{
  currentMillis = millis();
  if (customKey == '*' )
  {
    currentMillis = millis();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    ScreenTwo = HIGH;
  }
}

if (ScreenTwo == HIGH)  //if we are showing screen 2
{
  if (currentMillis - startMillis >= ScreenRefresh)  //if the period has passed
  {
    CoopDoor();  //refresh the screen
    startMillis = currentMillis;  //save the time that it was refreshed
    ClearScreen();
  }
}
}

void ClearScreen ()
{
  ScreenTwo = LOW;
  lcd.clear();
  MainMenu();

and it ends without a }

A couple of other things
1 - you only need to update currentMillis once. Usually it is convenient to do this at the start of loop()
2 personally I would use a boolean for the ScreenTwo variable as the code reads better, at least to me

if (ScreenTwo == true)  //if we are showing screen 2

I added you code and took your advice, but I can't get CoopDoor(); to display, if I go to the second screen it's blank where closed and open should be. It's like it's not running CoopDoor(); in that millis function. I tried moving CoopDoor(); back into the screen 2 function, but that didn't solve anything. I also verified that radio connection was happening by using an other stable script.

#include <Keypad.h>
#include <LiquidCrystal.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long ScreenRefresh = 5000;


boolean ScreenTwo = false;
boolean doorState = LOW;

//Radio
int msg[1];
RF24 radio(2, 4);
const uint64_t pipes[2] = {
  0xF0F0F0F000LL, 0xF0F0F0F0FFLL
};

//LCD
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

//Keypad
const byte ROWS = 4;
const byte COLS = 3;

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[ROWS] = {3, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10};

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

void setup() {
  radio.begin();
  radio.setDataRate(RF24_250KBPS);
  radio.setChannel(100);
  radio.setRetries(15, 15);
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1, pipes[1]);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  startMillis = millis();

  //Screen 1
  lcd.begin(16, 2);
  MainMenu();
  Serial.begin(9600);
}

void loop() {
  //Timing
  currentMillis = millis();

  //Keypad
  char customKey = customKeypad.getKey();
  if (customKey) {
    Serial.println(customKey);
  }

  //Radio Code
  if (radio.available()) {
    bool done = false;
    while (!done) {
      done = radio.read(msg, 1);

      if (msg[0] == 111) {
        doorState = HIGH;
      }

      else if (msg[0] == 112) {
        doorState = LOW;
      }
    }
  }

  // Screen 2
  if (customKey == '*' ) {
    currentMillis = millis();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    ScreenTwo = true;
  }

  if (ScreenTwo == HIGH)  //if we are showing screen 2
  {
    if (currentMillis - startMillis >= ScreenRefresh)  //if the period has passed
    {
      CoopDoor();  //refresh the screen
      startMillis = currentMillis;  //save the time that it was refreshed
      ClearScreen();
    }
  }
}
void CoopDoor() {
  if (doorState == HIGH) {
    lcd.setCursor(0, 1);
    lcd.print("Open");
  }
  else if (doorState == LOW) {
    lcd.setCursor(0, 1);
    lcd.print("Closed");
  }
}

void MainMenu () {
  lcd.setCursor(0, 0);
  lcd.print("* for Coop");
  lcd.setCursor(0, 1);
  lcd.print("# for Security");
}

void ClearScreen ()
{
  ScreenTwo = LOW;
  lcd.clear();
  MainMenu();
}

boolean variables should be either true or false, not HIGH and LOW so the first thing to do is to change

  ScreenTwo = LOW;

to

  ScreenTwo = false;

It should not make a difference but should really be corrected.

If that does not work then add some Serial.print()s showing which program section is being executed and the value of pertinent variables

Ok cool! My little 10 uf capacitor's solder connection (on the rf module) was a little finicky too, so I'll get that all fixed and test it again.

Hi! So i worked with the code some again and I realized that I needed to include CoopDoor(); in screen 2, in order to have something to refresh off of. Now I'm running into another issue.

After CoopDoor(); becomes open instead of closed, it won't switch back to closed until I rest the arduino (probably because of the boolean DoorState = LOW at the begining of the script). So for some reason DoorState isn't switching from high to low. I also have verified that the rf module is sending the correct information to the arduino.

Thanks!

#include <Keypad.h>
#include <LiquidCrystal.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>
#include <SPI.h>

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long ScreenRefresh = 5000;

boolean ScreenTwo = false;
boolean RefreshDone = false;

boolean doorState = LOW;

//Radio
int msg[1];
RF24 radio(2, 4);
const uint64_t pipes[2] = {
  0xF0F0F0F000LL, 0xF0F0F0F0FFLL
};

//LCD
LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);

//Keypad
const byte ROWS = 4;
const byte COLS = 3;

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[ROWS] = {3, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10};

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

void setup() {
  radio.begin();
  radio.setDataRate(RF24_250KBPS);
  radio.setChannel(100);
  radio.setRetries(15, 15);
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1, pipes[1]);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  startMillis = millis();

  //Screen 1
  lcd.begin(16, 2);
  MainMenu();
  Serial.begin(9600);
}

void loop() {
  //Timing
  currentMillis = millis();

  //Keypad
  char customKey = customKeypad.getKey();
  if (customKey) {
    Serial.println(customKey);
  }

  //Radio Code
  if (radio.available()) {
    bool done = false;
    while (!done) {
      done = radio.read(msg, 1);

      if (msg[0] == 111) {
        doorState = HIGH;
      }

      else if (msg[0] == 112) {
        doorState = LOW;
      }
    }
  }

  // Screen 2
  if (customKey == '*' ) {
    currentMillis = millis();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("The Coop Door Is");
    ScreenTwo = true;
    CoopDoor();
  }

  if (ScreenTwo == HIGH) {
    if (currentMillis - startMillis >= ScreenRefresh) {
      CoopDoor();
      startMillis = currentMillis;  //save the time that it was refreshed
      RefreshDone = true;
    }
  }

  if (RefreshDone == true) {
    ClearScreen();
    RefreshDone = false;
  }
}

void CoopDoor() {
  if (doorState == HIGH) {
    lcd.setCursor(0, 1);
    lcd.print("Open");
  }
  else if (doorState == LOW) {
    lcd.setCursor(0, 1);
    lcd.print("Closed");
  }
}

void MainMenu () {
  lcd.setCursor(0, 0);
  lcd.print("* for Coop");
  lcd.setCursor(0, 1);
  lcd.print("# for Security");
}

void ClearScreen ()
{
  ScreenTwo = false;
  lcd.clear();
  MainMenu();
}