Translate

Showing posts with label time. Show all posts
Showing posts with label time. Show all posts

Saturday, 24 May 2014

Room Management System – Adding a little more control to the AC's part 2


Today we are back at the breadboard. We add a LM 35 temperature sensor to our test circuit. If we look at the flat side of the sensor, the left pin connects to 5V supply voltage, the right pin to ground and the centre pin we connect to Atmega pin 25 (analogue pin 2).



Back to building the code. We start again adding a few more variables into the declaration part of our sketch. Staying pretty much in the beginning, we add:

#define DS1307_ADDRESS 0x68

byte zero = 0x00;

//uncomment the lines below for debugging

//#define DA_DEBUG_serial //to enable Serial monitor

//#define DA_DEBUG_in //debug the input stage

//>>>>>>>>>>>>>ADD the line below<<<<<<<<<<<<<<

//#define DA_DEBUG_tmp //check temperature readings

//#define DA_DEBUG_holtimers //debug the holiday timers

//#define DA_DEBUG_out //debug the output stage

//#define DA_DEBUG_photo //debug control from photocell for holiday switching

/////////////////////Declaring the Variables/////////////////

Now we go into the “Timer and Sensitivity Settings” section and add:

int dAC3 = 120; //delay time in seconds for AC 3 (bed3)

int dAC4 = 120; //delay time in seconds for AC 4 (living)

int dMaster = 240; //delay time in seconds for Master Off

//>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<<

byte ac_op_mode = 2; //ac mode 1) read switches only

//ac mode 2) limited control using ac switch or auto restart of AC

//ac mode 3) limited control using ac momentary switch (push button)

byte ac_set_temp = 28; //temperature at which the AC switches on

//>>>>>>>>>>>>>Addition ends here<<<<<<<<<<<<<

byte ac_periode[4][2] = {

{1, 5}, //time periode between January and May

{6, 9}, //time periode between June and September

{10, 10}, //October

{11, 12} //time periode between November and December

};

byte ac_master_bypass[4] = {0}; //array holding values to bypass master relay

//////////////////////holiday and AC timer settings//////////////////////

From her we go down just a little further and in the “Defining the Arduino pins” section we add:

const byte TMP01 = 2; //Arduino analogue pin 2 - temperature sensor

Just a little further down in the “All the other variables” section we add:

///////////////////////////all the other variables/////////////////////////////

///////////////////////////////////////////////////////////////////////////////

////Sensor and timer variables

int delayTime[16] = {dBed1, dBed2, dBed3, dLiving, dBath1, dBath2, dBath3,

dBath4, dKitchen, dCorridor, dAC1, dAC2, dAC3, dAC4,

dMaster, 0};

//>>>>>>>>>>>>>ADD the line below<<<<<<<<<<<<<

byte temperatur1 = 0; //holding temperature for room 1

int sensorValue = 0; //holding the indicated sensor value of the photocell

byte photocellSwitch = 0; //holding the switch command after 

Now we are going down in to the main loop and add after the “checking the light status” section the code for the temperature sensor:

//////////////Debug Statements//////////////////////////////////

#ifdef DA_DEBUG_in // (RRKM-01) Only when debugging

for(int x=0; x<23; x++) {

Serial.println( "Status of Switch S" + String(x+1) + ": " +

IIFs((switchState[x] == ON), "ON", "OFF" )

);

}

#endif

//////////////////checking the light status (photo cell)//////////////////////

sensorValue = 0;

int reading = 0; //the readings

for(int i=0; i<15; i++){ //take 15 readings

reading += analogRead(lightSensor);

}

//average the readings

sensorValue = reading / 15;

//Serial.print("Sensor value: ");

//Serial.println(sensorValue);

//>>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<<

///////////////Checking the room temperature/////////////

int temp1 = 0; //var to take the readings

for(int i=0; i<15; i++){ //take 15 readings for a stable output

temp1 += map(analogRead(TMP01),0,410,-40,125);

}

temperatur1 = temp1/15; //average the readings

#ifdef DA_DEBUG_tmp //check the output

Serial.print("Current Temperature: ");

Serial.println(temperatur1);

#endif

//>>>>>>>>>>>>>>Addition ends here<<<<<<<<<<<<<<

//////////////////processing the input/////////////////////

Lets have a quick look into the map statement “map(analogRead(TMP01),0,410,-40,125);”

map is a telling the system to convert a range of values into another range of values. I guess we can skip the analogRead part of it and go straight to the numbers. Our analogue input will read values between 0 and 1023 on a max. allowed input voltage range between 0 an 5 Volt. To convert the analogue reading into a voltage reading we would need to type in the command
map(analogRead(analogPin)0, 1023, 0, 5); This would convert the analogue Range between 0 and 1024 to our voltage range between 0 and 5.

I am using a TMP36 sensor, which is giving a output voltage from 100 to 2000 mV having a maximum operating range from -50 and 150 degrees Celsius. A lot of tutorials using the max values to map the range. I had a hell of a time to find out why the temperature value was always about 5 to 6 degrees above the actual temperature. If we look a little closer into the data sheet of the TMP36, we also find a line:
Scale Factor, TMP36 -40°C ≤ T + 125°C
This are the values we have to work with!
Going back to our map command. Since the analogue pin is set to measure up to 5 V by default, we have to tell the system, that we only have a voltage range from 100 mV (0,1 Volt set to 0) and 2000 mV (2 Volt).
I was taking it very simple and divided the max output value 1023 by 5 max default Voltage which gives us a result of 204.8. This multiplied by 2 Volt (max sensor output) comes up to 409.6 (set to 410).
This makes the first two numbers in the map command. The second two numbers are the range we are measuring, -40°C to 125°C.
There we are:

temp1 += map(analogRead(TMP01)0,410,-40,125).


Since we have implemented a temperature sensor and passed the reading to a variable so we can use it throughout the rest of the sketch, we go down to the ac_read() function and insert the temperature check. If you did pay attention at the beginning, when we added the variables, we also added one called “byte ac_op_mode = 2;”. If you remember the first post about implementing a little more control over the AC-units, we talked about 3 different ways of controlling it. The first on was using the AC's controller for general functions and use the Room Management System only to shut it of when a door or window is opened for more than a predefined time. The second option was to be able switching the AC's on and off according to timer and sensor settings outside the Controls using eider the AC's Auto restart ability or if the AC unit has a toggle switch to turn it of or on where we can plug in to. Option three was the same as option 2 but having a momentary switch (push button) to plug into. In this post we will take care of option 1 and 2 and as just mentioned above the implementation of temperature control. There fore we go down to the ac_read() function.

Th first thing we do, we rebuild the function above or below and start with:

unsigned long ac_read(byte readSw, byte room, unsigned long light){

}

Next, we cut and paste the line where we get the seasonal periode:

unsigned long ac_read(byte readSw, byte room, unsigned long light){

byte periode = get_ac_periode(); //function call to check time periode (season)

}

We do the same with the next if-statement where we check if a time is set to switch on the unit and add a statement checking for the temperature.

unsigned long ac_read(byte readSw, byte room, unsigned long light){

byte periode = get_ac_periode(); //function call to check time periode (season)

if(ac_forced_on[room-10][0] != 99 && checkOnTime(room_timers[room][periode][0], room_timers[room][periode][1],

room_timers[room][periode][2], room_timers[room][periode][3]) == 1 &&

temperatur1 >= ac_set_temp){ //<<<<<<<<<<<<<NOTE the temperature check<<<<<<<<<<<<<

priorityStatus[room] = 1; //set priority status to 1 if yes

}

else{

priorityStatus[room] = 0; //set priority status to 0 if no

}

}

After that we add the mode structure:


unsigned long ac_read(byte readSw, byte room, unsigned long light){

byte periode = get_ac_periode(); //function call to check time periode (season)

if(ac_forced_on[room-10][0] != 99 && checkOnTime(room_timers[room][periode][0], room_timers[room][periode][1],

room_timers[room][periode][2], room_timers[room][periode][3]) == 1 &&

temperatur1 >= ac_set_temp){ //<<<<<<<<<<<<<NOTE the temperature check<<<<<<<<<<<<<

priorityStatus[room] = 1; //set priority status to 1 if yes

}

else{

priorityStatus[room] = 0; //set priority status to 0 if no

}

if(ac_op_mode == 1){

//add code for mode 1

}

else if(ac_op_mode == 2){

//add code for mode 2

}

else if(ac_op_mode == 3){

//add code for mode 3

}

}

Remember the code we had, just after we converted the the code for the AC units into a function, switching the AC's off soon as a window or a door is opened? If you still have it somewhere, just copy and paste it into the part where I made the note “//add code for mode 1. Second, cut the rest of the code from the previous ac_read() function and paste it in the one we just rebuild into the part where I wrote the note “add code for mode 2”:


unsigned long ac_read(byte readSw, byte room, unsigned long light){

byte periode = get_ac_periode(); //function call to check time periode (season)

//check if a forced switch on is defined and the set temperature

if(ac_forced_on[room-10][0] != 99 && checkOnTime(room_timers[room][periode][0], room_timers[room][periode][1],

room_timers[room][periode][2], room_timers[room][periode][3]) == 1 &&

temperatur1 >= ac_set_temp){

priorityStatus[room] = 1; //set priority status to 1 if yes

}

else{

priorityStatus[room] = 0; //set priority status to 0 if no

}

if(ac_op_mode == 1){

if(switchState[readSw] == 1 && lightStatus[14] == 1){ //Checking if readswitches are activated

//and the master relay is on AC room 1 (bed1)

lightOutput[room] = light; //providing the ability to

//switch on the AC

lightStatus[room] = 1; //setting the light (AC) status

roomTimer[room] = millis()/1000; //setting the timer

}

else if(switchState[readSw] == 0 && lightStatus[14] == 1){ //if a door is opened and the master

//relay is on

currentTime = millis()/1000; //setting time reference

endTime = currentTime - roomTimer[room]; //calculating the inactive time

if(endTime >= delayTime[room]){ //comparing inactive time with

//delay time

lightOutput[room] = 0; //cancelling ability to switch on the

//AC

lightStatus[room] = 0; //resetting the light (AC) status

roomTimer[room] = 0; //resetting the timer

}

}

}

else if(ac_op_mode == 2){

if(ac_master_bypass[room - 10] == 0){ //check if the master bypass is set

if(switchState[readSw] == 1 && lightStatus[14] == 1){ //Checking if readswitches are activated

if(checkOnTime(room_timers[room][periode][0], room_timers[room][periode][1], //checking if AC is allowed to run

room_timers[room][periode][2], room_timers[room][periode][3]) == 1){

lightOutput[room] = light; //switch on the AC

lightStatus[room] = 1; //setting the light (AC) status

roomTimer[room] = millis()/1000; //setting the timer

}

}

else if(switchState[readSw] == 1 && priorityStatus[room] == 1){ //if doors and windows are closed and priority is set

if(currentHour >= ac_forced_on[room-10][0] && currentMinute >= ac_forced_on[room-10][1]){ //check if it's time to start

lightOutput[room] = light; //switch on the AC

lightStatus[room] = 1; //setting the light (AC) status

roomTimer[room] = millis()/1000; //setting the timer

}

else{

lightOutput[room] = 0; //keep it off

lightStatus[room] = 0; //setting the light (AC) status

roomTimer[room] = 0; //resetting the room timer

}

}

else if(switchState[readSw] == 0 && lightStatus[14] == 1){ //if a door is opened and the master

//relay is on

currentTime = millis()/1000; //setting time reference

endTime = currentTime - roomTimer[room]; //calculating the inactive time

if(endTime >= delayTime[room]){ //comparing inactive time with

//delay time

lightOutput[room] = 0; //cancelling ability to switch on the

//AC

lightStatus[room] = 0; //resetting the light (AC) status

roomTimer[room] = 0; //resetting the timer

}

}

}

else if(ac_master_bypass[room - 10] == 1){

if(switchState[readSw] == 1){ //Checking if readswitches are activated

if(checkOnTime(room_timers[room][periode][0], room_timers[room][periode][1],

room_timers[room][periode][2], room_timers[room][periode][3]) == 1 &&

temperatur1 >= ac_set_temp){

lightOutput[room] = light; //providing the ability to

//switch on the AC

lightStatus[room] = 1; //setting the light (AC) status

roomTimer[room] = millis()/1000; //setting the timer

}

}

else if(switchState[readSw] == 0){ //if a door is opened and the master

//relay is on

currentTime = millis()/1000; //setting time reference

endTime = currentTime - roomTimer[room]; //calculating the inactive time

if(endTime >= delayTime[room]){ //comparing inactive time with

//delay time

lightOutput[room] = 0; //cancelling ability to switch on the

//AC

lightStatus[room] = 0; //resetting the light (AC) status

roomTimer[room] = 0; //resetting the timer

}

}

}

}

else if(ac_op_mode == 3){

//still being worked on

}

return lightOutput[room];

}

Please don't forget to delete what's left from the old ac_read() function or you will get a compiler error.
In the next post we take care of the possibility of the AC switch is a momentary switch (push button.

Sunday, 18 May 2014

Room Management System – Some improvements and bug fixes


First thing a big THANK YOU to Rene who is a great help in optimizing the code.

Currently I am addressing a simpler way of debugging,
a small bug fix in the way we are collecting the data from the CD4021B input shift registers
and removing the possibility of an error in case of RTC read problems.

For debugging purposes, I have plenty of “//Serial.print(value)” statements in the code which is a bit of a pain, finding them all, uncommenting them for debugging and commenting them out again after finishing the debug.
One of the things Rene brought to my attention was to define a debug mode and run it within #ifdef and #endif like:

//#define DEBUG_DA

#ifdef DEBUG_DA

for(int i=0; i<24; i++){

Serial.print(some text );

Serial.print(i);

Serial.print( :);

Serial.println(value[i]);

}

#endif

The code within #ifdef and #endif is ignored by the compiler as long as the corresponding #define is commented out.
That makes debugging a lot easier removing only a couple of “/” in the declaration part of the sketch rather than going through the whole sketch and finding all the needed “Serial.print” statements.

That's what I have done so far:

//#define DEBUG_DA

#ifdef DEBUG_DA

for(int i=0; i<24; i++){

Serial.print(some text );

Serial.print(i);

Serial.print( :);

Serial.println(value[i]);

}

#endif

Next jump is down to the main loop to where it says “do something with the collected data”

and change the following debug block

/////////////do something with the collected Data/////////////////////

//checks for debugging

//Serial.println(); //debug only

//Serial.print("Switch variable 1: "); //debug only

//Serial.println(switchVar1, BIN); //debug only

//Serial.println("-------------------"); //debug only

//Serial.println(); //debug only

//Serial.print("Switch variable 2: "); //debug only

//Serial.println(switchVar2, BIN); //debug only

//Serial.println("-------------------"); //debug only

//Serial.println(); //debug only

//Serial.print("Switch variable 3: "); //debug only

//Serial.println(switchVar3, BIN); //debug only

//Serial.println("-------------------"); //debug only
 
to

/////////////do something with the collected Data/////////////////////

#ifdef DA_DEBUG_in

Serial.println(); //debug only

Serial.print("Switch variable 1: "); //debug only

Serial.println(switchVar1, BIN); //debug only

Serial.println("-------------------"); //debug only

Serial.println(); //debug only

Serial.print("Switch variable 2: "); //debug only

Serial.println(switchVar2, BIN); //debug only

Serial.println("-------------------"); //debug only

Serial.println(); //debug only

Serial.print("Switch variable 3: "); //debug only

Serial.println(switchVar3, BIN); //debug only

Serial.println("-------------------"); //debug only

#endif

and we do the same thing with the debug statements at the end of the part where we go through the single shift register pins and pass the results into the switchState[] array:

//////////////Debug Statements//////////////////////////////////

#ifdef DA_DEBUG_in

for(int c=0; c<22; c++){

Serial.print("Switch state: ");

Serial.print(c);

Serial.print(" / ");

Serial.println(switchState[c]);

delay(500);

}

#endif

//////////////////checking the light status//////////////////////

I also added a revised debug part between the photo cell checks and the Holiday light switching. We go down to the “Holiday lighting” section and add above:

#ifdef DA_DEBUG_photo

Serial.print("Photo cell switch: ");

Serial.println(photocellSwitch);

for(int c=0; c<17; c++){

Serial.print("Light level ");

Serial.print(c);

Serial.print(" :");

Serial.println(lightLevel[c]);

}

#endif

//////////////Holiday lighting/////////////////////////

From here we jump down to the end of the main loop to the “Output” section and change

///////////////////////////Output/////////////////////////////////////////////////

for(int i=0; i<17; i++) { //loop through the light output array

/*Serial.print("Light Output "); //debug only

Serial.print(i); //debug only

Serial.print(": "); //debug only

Serial.println(lightOutput[i]); //debug only

Serial.print("Light status: "); //debug only

Serial.println(lightStatus[i]); //debug only

Serial.print("Room Timer: "); //debug only

Serial.println(roomTimer[i]); //debug only

delay(500);*/

outputL += lightOutput[i]; //adding up the numbers

}

if(maintenancePin == 1) { //if maintenance switch is active

for(int i=1; i>17; i++){ //loop through all circuits

lightStatus[i] = 1; //setting the light status of everything

roomTimer[i] = millis()/1000; //setting all the room timers

}

outputL = 32767; //setting the output

//binary 0111111111111111

}

lcd.setCursor(0,0);

lcd.print(outputL, BIN);

//Serial.print("Output value: ");

//Serial.print(outputL);

//Serial.print(" ");

//Serial.println(outputL, BIN);

digitalWrite(latchPinOut, LOW); 

to

///////////////////////////Output/////////////////////////////////////////////////

for(int i=0; i<17; i++) { //loop through the light output array

#ifdef DA_DEBUG_out

Serial.print("Light Output "); //debug only

Serial.print(i); //debug only

Serial.print(": "); //debug only

Serial.println(lightOutput[i]); //debug only

Serial.print("Light status: "); //debug only

Serial.println(lightStatus[i]); //debug only

Serial.print("Room Timer: "); //debug only

Serial.println(roomTimer[i]); //debug only

delay(100);

#endif

outputL += lightOutput[i]; //adding up the numbers

}

if(maintenancePin == 1) { //if maintenance switch is active

for(int i=1; i>17; i++){ //loop through all circuits

lightStatus[i] = 1; //setting the light status of everything

roomTimer[i] = millis()/1000; //setting all the room timers

}

outputL = 32767; //setting the output

//binary 0111111111111111

}

lcd.setCursor(0,0);

lcd.print(outputL, BIN);

#ifdef DA_DEBUG_out

Serial.print("Output value: ");

Serial.print(outputL);

Serial.print(" ");

Serial.println(outputL, BIN);

#endif

digitalWrite(latchPinOut, LOW); //setting the latch pin to low to

Let's have a look at the little “bug” in the data collection from the input shift register. There fore we go back up to

/////////////do something with the collected Data/////////////////////

//checks for debugging

#ifdef DA_DEBUG_in

Serial.println(); //debug only

Serial.print("Switch variable 1: "); //debug only

Serial.println(switchVar1, BIN); //debug only

Serial.println("-------------------"); //debug only

Serial.println(); //debug only

Serial.print("Switch variable 2: "); //debug only

Serial.println(switchVar2, BIN); //debug only

Serial.println("-------------------"); //debug only

Serial.println(); //debug only

Serial.print("Switch variable 3: "); //debug only

Serial.println(switchVar3, BIN); //debug only

Serial.println("-------------------"); //debug only

#endif

////////////loop through the 8 input pins to check their status////////////

for(int n=0; n<=7; n++){

Here we have a closer look at the for loop:

I am going through the shift registers 8 times even the data is read only once. At this stage we can eleminate the for loop complete.

for(int n=0; n<=7; n++){ //<<<<<<<<<<<<<DELETE<<<<<<<<<<<<

//shift register 1

if(switchVar1 & (1 << 0)) { //checking S1

//Serial.println("Switch 1 was activated."); //debug only

switchState[0] = 1;

}

else {

switchState[0] = 0;

}

if(switchVar1 & (1 << 1)) { //checking S2

//Serial.println("Switch 2 was activated."); //debug only

switchState[1] = 1;

}

else {

switchState[1] = 0;

}

if(switchVar1 & (1 << 2)) { //checking S3

//Serial.println("Switch 3 was activated."); //debug only

switchState[2] = 1;

}

else {

switchState[2] = 0;

}

if(switchVar1 & (1 << 3)) { //checking S4

//Serial.println("Switch 4 was activated."); //debug only

switchState[3] = 1;

}

else {

switchState[3] = 0;

}

if(switchVar1 & (1 << 4)) { //checking S8

//Serial.println("Switch 8 was activated."); //debug only

switchState[7] = 1;

}

else {

switchState[7] = 0;

}

if(switchVar1 & (1 << 5)) { //checking S7

//Serial.println("Switch 7 was activated."); //debug only

switchState[6] = 1;

}

else {

switchState[6] = 0;

}

if(switchVar1 & (1 << 6)) { //checking S6

//Serial.println("Switch 6 was activated."); //debug only

switchState[5] = 1;

}

else {

switchState[5] = 0;

}

if(switchVar1 & (1 << 7)) { //checking S5

//Serial.println("Switch 5 was activated."); //debug only

switchState[4] = 1;

}

else {

switchState[4] = 0;

}

//shift register 2

if(switchVar2 & (1)) { //checking S9

//Serial.println("Switch 9 was activated."); //debug only

switchState[8] = 1;

}

else {

switchState[8] = 0;

}

if(switchVar2 & (1 << 1)) { //checking S10

//Serial.println("Switch 10 was activated."); //debug only

switchState[9] = 1;

}

else {

switchState[9] = 0;

}

if(switchVar2 & (1 << 2)) { //checking S11

//Serial.println("Switch 11 was activated."); //debug only

switchState[10] = 1;

}

else {

switchState[10] = 0;

}

if(switchVar2 & (1 << 3)) { //checking S12

//Serial.println("Switch 12 was activated."); //debug only

switchState[11] = 1;

}

else {

switchState[11] = 0;

}

if(switchVar2 & (1 << 4)) { //checking S16

//Serial.println("Switch 16 was activated."); //debug only

switchState[15] = 1;

}

else {

switchState[15] = 0;

}

if(switchVar2 & (1 << 5)) { //checking S15

//Serial.println("Switch 15 was activated."); //debug only

switchState[14] = 1;

}

else {

switchState[14] = 0;

}

if(switchVar2 & (1 << 6)) { //checking S14

//Serial.println("Switch 14 was activated."); //debug only

switchState[13] = 1;

}

else {

switchState[13] = 0;

}

if(switchVar2 & (1 << 7)) { //checking S13

//Serial.println("Switch 13 was activated."); //debug only

switchState[12] = 1;

}

else {

switchState[12] = 0;

}

//shift register 3

if(switchVar3 & (1)) { //checking S17

//Serial.println("Switch 17 was activated."); //debug only

switchState[16] = 1;

}

else {

switchState[16] = 0;

}

if(switchVar3 & (1 << 1)) { //checking S18

//Serial.println("Switch 18 was activated."); //debug only

switchState[17] = 1;

}

else {

switchState[17] = 0;

}

if(switchVar3 & (1 << 2)) { //checking S19

//Serial.println("Switch 19 was activated."); //debug only

switchState[18] = 1;

}

else {

switchState[18] = 0;

}

if(switchVar3 & (1 << 3)) { //checking S20

//Serial.println("Switch 20 was activated."); //debug only

maintenancePin = 1;

}

else {

maintenancePin = 0;

}

if(switchVar3 & (1 << 4)) { //checking S21

//Serial.println("Switch 20 was activated."); //debug only

switchState[20] = 1;

}

else {

switchState[20] = 0;

}

if(switchVar3 & (1 << 5)) { //checking S22

//Serial.println("Switch 21 was activated."); //debug only

switchState[21] = 1;

}

else {

switchState[21] = 0;

}

if(switchVar3 & (1 << 6)) { //checking S23

//Serial.println("Switch 22 was activated."); //debug only

switchState[22] = 1;

}

else {

switchState[22] = 0;

}

}//<<<<<<<<<<<<<DELETE<<<<<<<<<<<<<

A small explanation why it is sructured like that:

The original plan was to go through the pins like

for(n=0; n<=7; n++){

if(switchVar1 & (1 << n)){

switchState[n] = 1;

}

else {

switchState[n] = 0;

}

if(switchVar2 & (1 << n)){

switchState[n + 8] = 1;

}

else {

switchState[n + 8] = 0;

}

if(switchVar3 & (1 << n)){

switchState[n + 16] = 1;

}

else {

switchState[n + 16] = 0;

}

}

While building a prototype, I run into a problem with the pin layout of the shift register which forced me to cross the tracks on the circuit board. The compromise resulted in the current structure of not having a parallel linearity in the pin count and the count in the switchState[] array which prevents us of using the for loops as planned above. I know, it's a few more lines of code and thinking it through after wards, there would have been another way. But that would have caused of not having a linearity in the output shift register.
May be if I get a couple of more years experience in coding, I might find a way around it (-:.

Now we take care of an issue at the RTC read statement.

//////////////////processing the input/////////////////////

if(RTC.read(tm)) { //Reading the clock

currentHour = tm.Hour; //passing the time into a var

currentMinute = tm.Minute; //passing the time into a var

currentDay = tm.Wday - 1; //passing Weekday

//(Mon - Sun eg 1-7) into var

currentDoM = tm.Day; //passing day in to var (1-31)

currentMonth = tm.Month; //passing month into var (1-12)

currentYear = tmYearToCalendar(tm.Year); //passing year to var

}

It doesn't seem to be anything wrong with it but what happens when RTC.read fails? Being honest, I would have assumed nothing since the if statement will only be processed if RTC.read does read something. OK, that's the point “read something”.

For now we just move the part below, where we are printing the time and date into the if statement “if(RTC.read(tm))” and print a error message in a corresponding “else” statement in case RTC.read fails. Going through the readings and check if they are within the required range and to check if they make sens (comparing them to the last reading) is a pretty complex error handling routine, which I will take care of soon as the Menu is completed.

The revised part now looks like:

//////////////////processing the input/////////////////////

if(RTC.read(tm)) { //Reading the clock

currentHour = tm.Hour; //passing the time into a var

currentMinute = tm.Minute; //passing the time into a var

currentDay = tm.Wday - 1; //passing Weekday

//(Mon - Sun eg 1-7) into var

currentDoM = tm.Day; //passing day in to var (1-31)

currentMonth = tm.Month; //passing month into var (1-12)

currentYear = tmYearToCalendar(tm.Year); //passing year to var

//>>>>>>>>>>>>>The part below moved from

//just outside the “if(RTC.read statement) <<<<<<<<<<<<<

lcd.setCursor(0, 0); //set the corsor to line 1 pos 1

lcd.print(" "); //print 15 blanks to delete all

//prior statements

lcd.setCursor(0, 1); //set cursor to row 2 pos 1

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(weekday_table[currentDay]))));

lcd.print(" ");

if(currentDoM < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(currentDoM);

lcd.write(pgm_read_byte(&char_table[4])); //print dott

if(currentMonth < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(currentMonth);

lcd.print(" ");

if(currentHour < 10) lcd.write(pgm_read_byte(&char_table[2]));

//if the hour is less than 10

//we print a 0 to keep 2 digits

lcd.print(currentHour); //print current time (hour)

lcd.write(pgm_read_byte(&char_table[3])); //print seperator

if(currentMinute < 10) lcd.write(pgm_read_byte(&char_table[2]));

//if the minute is less than

//10 print 0 to keep 2 digits

lcd.print(currentMinute); //print current time (minutes)

//>>>>>>>>>>>>>The moved part ends here<<<<<<<<<<<<<

}

//>>>>>>>>>>>>>Added else statement<<<<<<<<<<<<<

else {

get_error(0, 1);

}

photocellSwitch = getSensorValue(sensorValue, photoCellCutOff,

photoCellCutOn, photocellSwitchOld);

photocellSwitchOld = photocellSwitch;

//allowing the lights to switch on between 17:00 and 23:00 h

if(photocellSwitch == 1 && currentHour >= 17 && currentHour <= 23) {

for(int i=0; i<10; i++){

lightLevel[i] = 1;

}

lightLevel[15] = 1;

}

//allowing the lights to switch on between 00:00 and 08:00 h

else if(photocellSwitch == 1 && currentHour >= 0 && currentHour <= 8){

for(int i=0; i<10; i++){

lightLevel[i] = 1;

}

Now you got me, I sneeked a new function in, get_error(0, 1). Nothing dramatic, at the end of the whole sketch we add:

void get_error(byte msg, byte row){ //function to print error message

lcd.setCursor(0, row); //set cursor to defined row

//print assigned message from the error_table

lcd.print(strcpy_P(buffer_M, (char*)pgm_read_word(&(error_table[msg]))));

}

I know, I am bad today. To store the error messages we want to print in the program memory, we have to go all the way back up into the declaration part to the Menu and user interface section and add just above the setup loop:


//>>>>>>>>>>>>>addition starts here<<<<<<<<<<<<<<

prog_char error_0[] PROGMEM = "RTC ERR";

prog_char error_1[] PROGMEM = "RTC Read ERR";

PROGMEM const char *error_table[] = {

error_0,

error_1

};

//>>>>>>>>>>>>>>addition ends here<<<<<<<<<<<<<

void setup() {

//////////////Start Serial for Debugging/////////////////////

#ifdef DA_DEBUG_serial

Serial.begin(9600);

#endif

Another discovery I made today. Since I had repeated problems with the lcd display going blank and the whole system resetting it self while I turned on the computer monitor or my mobile rang, I was all over to find out what's going on. First I thought its the display cause the LCD displays are a little fuzzy with noise. I checked it out and had it running on the Arduino board again with a dummy clock program only using the time library and it worked fine without any problems. Finally I dragged it down to an I2C issue. I just added a 3.3k pull up resistor between the RTC's SDA line and VCC and the RTC's SCL line and VCC.

Monday, 12 May 2014

Room Management System – The Menu – part 5


Since I have already installed 3 of the 10 needed sub menus, I am running a bit short on memory again. It went up again from 37% when we started building the menu to 53% of RAM usage after activating the third sub menu even there where not any additional functions etc. I don't think it has even anything to do with the program flow it self. My closest guess is the way we are passing the informative text messages to the functions. At this point we will change the way we are doing this. The messages being passed go in to the program memory together with all the other message strings. Instead of passing the string we will pass the number corresponding to the place in the message table and we will retrieve the string from the program memory from the function using it. Even not gaining a lot, I banned some numbers which been shown repeatingly and a few other messages into the program memory.

Let's start and jump right to the declaration part of the sketch and there in to the “menu and user interface” section:

byte submenu = 0; //var to count current submenu option

const byte submenus = 7; //available submenu options

char buffer_M[20]; //var holding the menu strings retrieved from

//the program memory

//Storing some menu messages in the program memory

prog_char msg_0[] PROGMEM = "Not Used";

prog_char msg_1[] PROGMEM = "Saving....";

prog_char msg_2[] PROGMEM = "Setup mode";

prog_char msg_3[] PROGMEM = "Starting....";

prog_char msg_4[] PROGMEM = "RMU 1.2.7";

prog_char msg_5[] PROGMEM = "Weekday";

/////////////////////////////////////////////////////////////////////////////////

//>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<

/////////////////////////////////////////////////////////////////////////////////

prog_char msg_6[] PROGMEM = "On TIMER Off";

prog_char msg_7[] PROGMEM = "Off ";

prog_char msg_8[] PROGMEM = "Active";

prog_char msg_9[] PROGMEM = "PIR Delay R";

prog_char msg_10[] PROGMEM = "T1 On/Off R";

prog_char msg_11[] PROGMEM = "T2 On/Off R";

prog_char msg_12[] PROGMEM = "T3 On/Off R";

prog_char msg_13[] PROGMEM = "ADJ Hour On";

prog_char msg_14[] PROGMEM = "ADJ Minute On";

prog_char msg_15[] PROGMEM = "ADJ Hour Off";

prog_char msg_16[] PROGMEM = "ADJ Minute Off";

//////////////////////////////////////////////////////////////////////////////////

//>>>>>>>>>>>>>Addition ends here<<<<<<<<<<<<<

//////////////////////////////////////////////////////////////////////////////////

Since we have everything stored in the program memory, we have to tell the accompanying table that it's there:

//Creating the table for the stored menu messages

PROGMEM const char *msg_table[] = {

msg_0,

msg_1,

msg_2,

msg_3,

msg_4,

msg_5,

//>>>>>>>>>>>>>addition starts here<<<<<<<<<<<<<

//don't forget the “,” after msg_5

msg_6,

msg_7,

msg_8,

msg_9,

msg_10,

msg_11,

msg_12,

msg_13,

msg_14,

msg_15,

msg_16

//>>>>>>>>>>>>>addition ends here<<<<<<<<<<<<<

};

We just move down to the next table and add a few numbers:

//storing some special char's in the program memory

const byte char_table[] PROGMEM = {

B01111110, //Arrow right

B01111111, //Arrow left

B00110000, //0

B00111010, //separator

B00101110, //dot

//>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<<

//don't forget to add “,” after B00101110

B00110001, //1

B00110010, //2

B00110011, //3

B00110100, //4

B00110101, //5

B00110110, //6

B00110111, //7

B00111000, //8

B00111001 //9

//>>>>>>>>>>>>>Addition ends here<<<<<<<<<<<<<

};

//storing the main menu points in the program memory

prog_char menu_0[] PROGMEM = "Date/Time";

prog_char menu_1[] PROGMEM = "Sensitivity";

Since we stored all this things in the program memory, let's use it. We do a long jump right down to the selectMenu() function and look at the sub menu we implemented in the last post:
Replace all statements “lcd.print("1"); //printing assigned room number”
with
lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

if(menuOption == 6) return; //and menu option is 6 return (not used)

if(menuOption == 7){ //and menu option is 7 (room 1)

subButton = 0; //resetting the button var

submenu = 1; //submenu counter

lcd.clear(); //clear screen

//retrieving and printing first sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[0]))));

lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

while(submenu < submenus){ //loop through the sub menu points

subButton = read_act_buttons(); //checking for pressed buttons

if(subButton == btnMenu){ //if button Menu was pressed

submenu++; //add 1 - move to the next sub menu point

if(submenu == 2){ //if we are at sub menu 2

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[1]))));

lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

}

if(submenu == 3){ //if we are at sub menu 3

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[2]))));

lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

}

if(submenu == 4){ //if we are at sub menu 4

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[3]))));

lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

}

if(submenu == 5){ //if we are at sub menu 5

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[4]))));

lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

}

if(submenu == 6){ //if we are at sub menu 6

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[5]))));

lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

}

if(submenu == 7){ //if we are at sub menu 7

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[6]))));

lcd.write(pgm_read_byte(&char_table[5])); //printing assigned room number 1

}

}

if(subButton == btnSelect){ //if we pressed btnSelect

if(submenu == 1){ //and submenu is 1

//call the function get_delay() to change the setting

delayTime[0] = get_delay("R1 PIR Delay", dBed1);

return;

}

if(submenu == 2){ //and sub menu is 2

//call the function get_offon to change the setting

room1MActive = get_offon("R1 T1 On/Off", room1MActive);

return;

}

if(submenu == 3){ //and submenu is 3

//call the function get_setTime() to change timer 1

get_setTime("R1 T1 On/Off", room1OnM[0], room1OnM[1],

room1OffM[0], room1OffM[1], 1);

return;

}

if(submenu == 4){ //and submenu is 4

//call the function get_offon() to change the setting

room1O1Active = get_offon("R1 T2 On Off", room1O1Active);

return;

}

if(submenu == 5){ //and submenu is 5

//call the function get_setTime() to change timer 2

get_setTime("R1 T2 On/Off", room1On1[0], room1On1[1],

room1Off1[0], room1Off1[1], 2);

return;

}

if(submenu == 6){ //and submenu is 6

//call the function get_offon() to change the setting

room102Active = get_offon("R1 T3 On Off", room102Active);

return;

}

if(submenu == 7){ //and submenu == 7

//call function get_setTime() to change timer 3

get_setTime("R1 T3 On/Off", room1On2[0], room1On2[1],

room1Off2[0], room1Off2[1], 3);

return;

}

}

}

} //submenu end

Next we look at our function calls:

if(submenu == 1){ //and submenu is 1

//call the function get_delay() to change the setting

delayTime[0] = get_delay("R1 PIR Delay", dBed1);

return;

}

Remember, we stored the string “R1 PIR Delay” as “PIR Delay R” in the program memory.
We replace the string with the msg_table place holder,
prog_char msg_9[] PROGMEM = "PIR Delay R";
and we add the room number we are dealing with, in that case it's room 1.

Our function call has to read now

if(submenu == 1){ //and submenu is 1

//call the function get_delay() to change the setting

delayTime[0] = get_delay(9, 1, dBed1);

return;

}

Let's go straight to the function get_delay() and change it so it fits our new function call.

The function entry

int get_delay(char delayText[], int reading){

changes to

int get_delay(byte info, byte room, int reading){

and the print statement

lcd.print(delayText); //print passed message

changes to

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[info])))); //print passed message

lcd.print(room);

Now, the complete revised function reads:

//function to adjust the pir delay time

int get_delay(byte info, byte room, int reading){

byte subButton = 0; //resetting the button value

byte value = reading / 60; //converting to Minutes

lcd.clear(); //clear screen

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[info])))); //print passed message

lcd.print(room);

lcd.setCursor(0, 1); //set cursor to second row, first column

if(value < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(value); //print the passed value in minutes

lcd.setCursor(4, 1); //set cursor to second row, column 6

lcd.print("Min"); //just print Min.

while(subButton != btnSelect){ //wait for select btn

subButton = read_act_buttons(); //check if a button was pressed

if(subButton == btnSearch){ //if btnSearch was pressed

if(value > 0 && value < 30){ //we are within allowed range

value++; //add 1 to value while btnSearch is pressed

}

if(value >= 30) value = 1; //if reaches upper limit set to lower limit

lcd.setCursor(0, 1); //setting the cursor

if(value < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(value); //printing the updated value

}

}

return value*60;

}

Now we go back where we left off in the selectMenu() function and look at the next function call:

if(submenu == 2){ //and sub menu is 2

//call the function get_offon to change the setting

room1MActive = get_offon("R1 T1 On/Off", room1MActive);

return;

}

We do the same changes as with the last function call. We look up the string and replace it with the place holder from the msg_table and again we add the room number we are dealing with. The function call after changes has to read like this:

if(submenu == 2){ //and sub menu is 2

//call the function get_offon to change the setting

room1MActive = get_offon(10, 1, room1MActive);

return;

}

Since we have this function call another 2 times, just with different variables to be passed, let's change them right away:

if(submenu == 4){ //and submenu is 4

//call the function get_offon() to change the setting

room1O1Active = get_offon("R1 T2 On Off", room1O1Active);

return;

}

has to change to:

if(submenu == 4){ //and submenu is 4

//call the function get_offon() to change the setting

room1O1Active = get_offon(11, 1, room1O1Active);

return;

}

and

if(submenu == 6){ //and submenu is 6

//call the function get_offon() to change the setting

room102Active = get_offon("R1 T3 On Off", room102Active);

return;

}

has to change to:

if(submenu == 6){ //and submenu is 6

//call the function get_offon() to change the setting

room102Active = get_offon(12, 1, room102Active);

return;

}

Next we go down to the function get_offon() and match it with the function call:

The function entry

byte get_offon(char offonText[], byte reading){

changes to

byte get_offon(byte info, byte room, byte reading){

the print statement

lcd.print(offonText); //print passed info text

changes to

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[info])))); //print passed info text

lcd.print(room);

We stay in the same function and replace the print statement

if(reading != 1) lcd.print(Off );

with

if(reading != 1) lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[7])))); //if value is

//not 1 timer is off

and

if(reading == 1) lcd.print(Active);

with

if(reading == 1) lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[8])))); //if timer

//is 1, print active

Please check careful, the last two print statements are contained twice in the function. We need to replace both of them.

Everything done? We go back again to the selectMenu() function where we just left off and have a look at the next function call. There I actually missed, that I was passing a informative string to be printed, which wasn't even used for something. So we just take out the part, where we pass it.

if(submenu == 3){ //and submenu is 3

//call the function get_setTime() to change timer 1

get_setTime("R1 T1 On/Off", room1OnM[0], room1OnM[1],

room1OffM[0], room1OffM[1], 1);

return;

}

changes to

if(submenu == 3){ //and submenu is 3

//call the function get_setTime() to change timer 1

get_setTime(room1OnM[0], room1OnM[1],

room1OffM[0], room1OffM[1], 1);

return;

}

We do the same thing with the other two similar function calls.

if(submenu == 5){ //and submenu is 5

//call the function get_setTime() to change timer 2

get_setTime("R1 T2 On/Off", room1On1[0], room1On1[1],

room1Off1[0], room1Off1[1], 2);

return;

}

changes to

if(submenu == 5){ //and submenu is 5

//call the function get_setTime() to change timer 2

get_setTime(room1On1[0], room1On1[1],

room1Off1[0], room1Off1[1], 2);

return;

}

and

if(submenu == 5){ //and submenu is 5

//call the function get_setTime() to change timer 2

get_setTime(room1On1[0], room1On1[1],

room1Off1[0], room1Off1[1], 2);

return;

}

to

if(submenu == 7){ //and submenu == 7

//call function get_setTime() to change timer 3

get_setTime(room1On2[0], room1On2[1],

room1Off2[0], room1Off2[1], 3);

return;

}

Again, we have a look at the corresponding function get_setTime() and do the changes accordingly:

byte get_setTime(char timeText[], 
                 byte   onTimeH, byte 
                 onTimeM, byte offTimeH,
                 byte offTimeM, byte 
                 room){

changes to

byte get_setTime(byte onTimeH, byte 
                 onTimeM, byte offTimeH,
                 byte offTimeM, byte 
                 room){

the statement

onTimeH = get_Timer("ADJ Hour On", onTimeH, 0, 23);

changes to

onTimeH = get_Timer(13, onTimeH, 0, 23);

the statement

onTimeM = get_Timer("ADJ Minute On", onTimeM, 0, 59);

is replaced with

onTimeM = get_Timer(14, onTimeM, 0, 59);

the statement

offTimeH = get_Timer("ADJ Hour Off", offTimeH, 0, 23);

changes to

offTimeH = get_Timer(15, offTimeH, 0, 23);

and

offTimeM = get_Timer("ADJ Minute Off", offTimeM, 0, 59);

is replaced by

offTimeM = get_Timer(16, offTimeM, 0, 59);

The complete revised function get_setTime reads now:

byte get_setTime( byte onTimeH,

byte onTimeM, byte offTimeH,

byte offTimeM, byte room){

byte subButton = 0;

onTimeH = get_Timer(13, onTimeH, 0, 23);

if(onTimeH >= 0 && onTimeH < 24){

onTimeM = get_Timer(14, onTimeM, 0, 59);

if(onTimeM < 60){

offTimeH = get_Timer(15, offTimeH, 0, 23);

if(offTimeH >= 0 && offTimeH < 24){

offTimeM = get_Timer(16, offTimeM, 0, 59);

if(offTimeM < 60){

lcd.clear();

lcd.print(strcpy_P(buffer_M, (char*)pgm_read_word(&(msg_table[6]))));

lcd.setCursor(0, 1);

if(onTimeH < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(onTimeH);

lcd.write(pgm_read_byte(&char_table[3])); //print separator

if(onTimeM < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(onTimeM);

lcd.setCursor(11, 1);

if(offTimeH < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(offTimeH);

lcd.write(pgm_read_byte(&char_table[3])); //print separator

if(offTimeM < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(offTimeM);

while(subButton != btnSelect){

subButton = read_act_buttons();

if(subButton == btnMenu) return 0;

if(subButton == btnSelect){

lcd.clear();

lcd.print(strcpy_P(buffer_M, (char*)pgm_read_word(&(msg_table[1]))));

delay(1000);

if(room == 1){

room1OnM[0] = onTimeH;

room1OnM[1] = onTimeM;

room1OffM[0] = offTimeH;

room1OffM[1] = offTimeM;

}

if(room == 2){

room1On1[0] = onTimeH;

room1On1[1] = onTimeM;

room1Off1[0] = offTimeH;

room1Off1[1] = offTimeM;

}

if(room == 3){

room1On2[0] = onTimeH;

room1On2[1] = onTimeM;

room1Off2[0] = offTimeH;

room1Off2[1] = offTimeM;

}

return 0;

}

}

}

}

}

}

}

I know, lots of changes and since we just touched the function get_Timer(), there are a few more to come. Let's start with the function get_Timer() itself:

int get_Timer(char timerText[], int reading, int startVal, int maxCount){

changes to

int get_Timer(byte info, int reading, int startVal, int maxCount){

and

lcd.print(timerText); //print the passed on info text

is replaced by

lcd.print(strcpy_P(buffer_M, (char*)pgm_read_word(&(msg_table[info])))); //print the passed on info text

Sorry, but since I forgot about the get_Timer function in the beginning, we have to go back up and add a few more variables to the msg_table and there fore we go back up to the declaration part, where it says “menu and user interface” and add:

//Storing some menu messages in the program memory

prog_char msg_0[] PROGMEM = "Not Used";

prog_char msg_1[] PROGMEM = "Saving....";

prog_char msg_2[] PROGMEM = "Setup mode";

prog_char msg_3[] PROGMEM = "Starting....";

prog_char msg_4[] PROGMEM = "RMU 1.2.7";

prog_char msg_5[] PROGMEM = "Weekday";

prog_char msg_6[] PROGMEM = "On TIMER Off";

prog_char msg_7[] PROGMEM = "Off ";

prog_char msg_8[] PROGMEM = "Active";

prog_char msg_9[] PROGMEM = "PIR Delay R";

prog_char msg_10[] PROGMEM = "T1 On/Off R";

prog_char msg_11[] PROGMEM = "T2 On/Off R";

prog_char msg_12[] PROGMEM = "T3 On/Off R";

prog_char msg_13[] PROGMEM = "ADJ Hour On";

prog_char msg_14[] PROGMEM = "ADJ Minute On";

prog_char msg_15[] PROGMEM = "ADJ Hour Off";

prog_char msg_16[] PROGMEM = "ADJ Minute Off";

///////////////////////////////////////////////////////////////////////////////////

//>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<<

///////////////////////////////////////////////////////////////////////////////////

prog_char msg_17[] PROGMEM = "Set Sensitivity";

prog_char msg_18[] PROGMEM = "Set photocell R";

prog_char msg_19[] PROGMEM = "Set photocell O";

prog_char msg_20[] PROGMEM = "ADJ Time Minute";

prog_char msg_21[] PROGMEM = "ADJ Time Hour";

prog_char msg_22[] PROGMEM = "ADJ Date Day";

prog_char msg_23[] PROGMEM = "ADJ Date Month";

prog_char msg_24[] PROGMEM = "ADJ Date Year";

//>>>>>>>>>>>>>Addition ends here<<<<<<<<<<<<<

And again, not to forget to add the new stored strings to the table below:

//Creating the table for the stored menu messages

PROGMEM const char *msg_table[] = {

msg_0,

msg_1,

msg_2,

msg_3,

msg_4,

msg_5,

msg_6,

msg_7,

msg_8,

msg_9,

msg_10,

msg_11,

msg_12,

msg_13,

msg_14,

msg_15,

msg_16,

//>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<<

//please don't forget the “,” after msg_16

msg_17,

msg_18,

msg_19,

msg_20,

msg_21,

msg_22,

msg_23,

msg_24

//>>>>>>>>>>>>>Addition ends here<<<<<<<<<<<<<

};

//storing some special char's in the program memory

const byte char_table[] PROGMEM = {

B01111110, //Arrow right

B01111111, //Arrow left

B00110000, //0

B00111010, //separator

And why it's so much fun doing this, we go back to the function selectMenu() and find the statement:

if(menuOption == 2){ //and menu option is 2

sensitivity = get_Timer("Set Sensitivity", sensitivity, 0, 1000); //go to function

return;

}

As we done before, we find the placeholder for the string “Set Sensitivity” and replace it with it, so the statement changes to:

if(menuOption == 2){ //and menu option is 2

sensitivity = get_Timer(17, sensitivity, 0, 1000); //go to function

return;

}

Next we find:

if(menuOption == 3){ //and menu option is 3

photoCellCutOff = get_Timer("Set photocell R", photoCellCutOff, 0, 1024); //go to function

return;

}

and replace it with

if(menuOption == 3){ //and menu option is 3

photoCellCutOff = get_Timer(18, photoCellCutOff, 0, 1024); //go to function

return;

}

The statement

if(menuOption == 5){ //and menu option is 5

photoOutsideOff = get_Timer("Set photocell O", photoOutsideOff, 0, 1024); //go to function

return;

}

changes to

if(menuOption == 5){ //and menu option is 5

photoOutsideOff = get_Timer(19, photoOutsideOff, 0, 1024); //go to function

return;

}

From here we go down to the function adjust_date_time(); and do the following changes:

byte minuteT = get_Timer("ADJ Time Minute", tm.Minute, 0, 59);

changes to

byte minuteT = get_Timer(20, tm.Minute, 0, 59);

and

byte hourT = get_Timer("ADJ Time Hour", tm.Hour, 0, 23);

changes to

byte hourT = get_Timer(21, tm.Hour, 0, 23);

The statement

byte monthDay = get_Timer("ADJ Date Day", tm.Day, 1, 31);

is replaced by

byte monthDay = get_Timer(22, tm.Day, 1, 31);

and

byte monthT = get_Timer("ADJ Date Month", tm.Month, 1, 12);

changes to

byte monthT = get_Timer(23, tm.Month, 1, 12);

and finally

byte yearT = get_Timer("ADJ Date Year", tmYearToCalendar(tm.Year)-2000, 0, 99);

is replaced by

byte yearT = get_Timer(24, tmYearToCalendar(tm.Year)-2000, 0, 99);

If you compile the sketch now, we are down to only 35% of RAM usage and the program memory is still only at 57%. That gives us now enough space for the rest of the sub menus. If you couldn't follow everything, don't worry to much. Soon as the menu is complete, I try to put another post with the fully updated sketch together.

Now we add the next submenu and there fore we go back right into the selectMenu() function. We start exactly below the “}” which we marked with “//submenu end”. If you look through, the structure is the same as the last sub menu. A few things we need to take care of, if we just copy and paste the sub menu. We do need to change a few variables. First, make sure to change the statement marked with “//printing assigned room number” to match the room you are working with. In this case we are at room 2 and the statement has to be:

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

Second, the function calls we just changed and pass as second variable the room number, we have to make sure, that this variable matches again with the room we are working with like:

delayTime[0] = get_delay(9, 2, dBed2);

For room 2 we also have only two timers set up so submenu 6 and submenu 7 are not used. So we add to the option selection

lcd.setCursor(0, 1);

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[0]))));

and if you have a look in the program memory storage for the msg_table[], the place holder 0 stands for “Not used”.
The same in the part where the functions are called, you will notice the missing function calls:

if(submenu == 6)return;

if(submenu == 7)return;

Now let's see what the complete submenu no 2 looks like:

if(submenu == 7){ //and submenu == 7

//call function get_setTime() to change timer 3

get_setTime(room1On2[0], room1On2[1],

room1Off2[0], room1Off2[1], 3);

return;

}

}

}

} //submenu end

///////////////////////////////////////////////////////////////////////////////////

//>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<<

///////////////////////////////////////////////////////////////////////////////////

if(menuOption == 8){ //and menu option is 8 (room 2)

subButton = 0; //resetting the button var

submenu = 1; //submenu counter

lcd.clear(); //clear screen

//retrieving and printing first sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[0]))));

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

while(submenu < submenus){ //loop through the sub menu points

subButton = read_act_buttons(); //checking for pressed buttons

if(subButton == btnMenu){ //if button Menu was pressed

submenu++; //add 1 - move to the next sub menu point

if(submenu == 2){ //if we are at sub menu 2

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[1]))));

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

}

if(submenu == 3){ //if we are at sub menu 3

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[2]))));

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

}

if(submenu == 4){ //if we are at sub menu 4

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[3]))));

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

}

if(submenu == 5){ //if we are at sub menu 5

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[4]))));

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

}

if(submenu == 6){ //if we are at sub menu 6

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[5]))));

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

lcd.setCursor(0, 1);

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[0]))));

}

if(submenu == 7){ //if we are at sub menu 7

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[6]))));

lcd.write(pgm_read_byte(&char_table[6])); //printing assigned room number 2

lcd.setCursor(0, 1);

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(msg_table[0]))));

}

}

if(subButton == btnSelect){ //if we pressed btnSelect

if(submenu == 1){ //and submenu is 1

//call the function get_delay() to change the setting

delayTime[0] = get_delay(9, 2, dBed2);

return;

}

if(submenu == 2){ //and sub menu is 2

//call the function get_offon to change the setting

room2MActive = get_offon(10, 2, room2MActive);

return;

}

if(submenu == 3){ //and submenu is 3

//call the function get_setTime() to change timer 1

get_setTime(room2OnM[0], room2OnM[1],

room2OffM[0], room2OffM[1], 11);

return;

}

if(submenu == 4){ //and submenu is 4

//call the function get_offon() to change the setting

room201Active = get_offon(11, 2, room201Active);

return;

}

if(submenu == 5){ //and submenu is 5

//call the function get_setTime() to change timer 2

get_setTime(room2On1[0], room2On1[1],

room2Off1[0], room2Off1[1], 12);

return;

}

if(submenu == 6)return;

if(submenu == 7)return;

}

}

} //submenu end

One final word to the changes we have to make in the sub menu. One closer look to the last variable in the get set time function call. Maybe you remember in the first submenu, working room 1, in the 3 get_setTime() function calls, the 3 last variables where 1, 2 and 3, standing in room 1 only for timer 1, 2 and 3. In room number 2, the 2 get_setTime() function calls contain 11 and 12 as last variables. I have to admit, being used to start counting at 0 and not at 1, the first digit of 11 and 12 stands for second room. The second digit of 11 and 12 stands for timer 1 and 2. This system goes through all the following rooms. Another thing we have to take care or is the fact, that the get_setTime() function does not return a variable. The variables are returned within the function and therefore we also have to update the function itself while adding sub menus.

Let's move to get_setTime():

byte get_setTime( byte onTimeH,

byte onTimeM, byte offTimeH,

byte offTimeM, byte room){

byte subButton = 0;

onTimeH = get_Timer(13, onTimeH, 0, 23);

if(onTimeH >= 0 && onTimeH < 24){

onTimeM = get_Timer(14, onTimeM, 0, 59);

if(onTimeM < 60){

offTimeH = get_Timer(15, offTimeH, 0, 23);

if(offTimeH >= 0 && offTimeH < 24){

offTimeM = get_Timer(16, offTimeM, 0, 59);

if(offTimeM < 60){

lcd.clear();

lcd.print(strcpy_P(buffer_M, (char*)pgm_read_word(&(msg_table[6]))));

lcd.setCursor(0, 1);

if(onTimeH < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(onTimeH);

lcd.write(pgm_read_byte(&char_table[3])); //print separator

if(onTimeM < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(onTimeM);

lcd.setCursor(11, 1);

if(offTimeH < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(offTimeH);

lcd.write(pgm_read_byte(&char_table[3])); //print separator

if(offTimeM < 10) lcd.write(pgm_read_byte(&char_table[2])); //print 0

lcd.print(offTimeM);

while(subButton != btnSelect){

subButton = read_act_buttons();

if(subButton == btnMenu) return 0;

if(subButton == btnSelect){

lcd.clear();

lcd.print(strcpy_P(buffer_M, (char*)pgm_read_word(&(msg_table[1]))));

delay(1000);

if(room == 1){ //room 1 timer 1

room1OnM[0] = onTimeH;

room1OnM[1] = onTimeM;

room1OffM[0] = offTimeH;

room1OffM[1] = offTimeM;

}

if(room == 2){ //room 1 timer 2

room1On1[0] = onTimeH;

room1On1[1] = onTimeM;

room1Off1[0] = offTimeH;

room1Off1[1] = offTimeM;

}

if(room == 3){ //room 1 timer 3

room1On2[0] = onTimeH;

room1On2[1] = onTimeM;

room1Off2[0] = offTimeH;

room1Off2[1] = offTimeM;

}

//>>>>>>>>>>>>>Addition starts here<<<<<<<<<<<<<

if(room = 11){ //room 2 timer 1

room2OnM[0] == onTimeH;

room2OnM[1] == onTimeM;

room2OffM[0] == offTimeH;

room2OffM[1] == offTimeM;

}

if(room = 12){ //room 2 timer 2

room2On1[0] == onTimeH;

room2On1[1] ==onTimeM;

room2Off1[0] ==offTimeH;

room2Off1[1] == offTimeM;

}

//>>>>>>>>>>>>>Addition ends here<<<<<<<<<<<<<

return 0;

}

}

}

}

}

}

}

As final for today, the submenu number 3 and looking at how things are done at the last addition, I think it is not to difficult to follow the changes and how they where done.

if(menuOption == 9){ //and menu option is 9 (room 3)

subButton = 0; //resetting the button var

submenu = 1; //submenu counter

lcd.clear(); //clear screen

//retrieving and printing first sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[0]))));

lcd.write(pgm_read_byte(&char_table[7])); //printing assigned room number 3

while(submenu < submenus){ //loop through the sub menu points

subButton = read_act_buttons(); //checking for pressed buttons

if(subButton == btnMenu){ //if button Menu was pressed

submenu++; //add 1 - move to the next sub menu point

if(submenu == 2){ //if we are at sub menu 2

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[1]))));

lcd.write(pgm_read_byte(&char_table[7])); //printing assigned room number 3

}

if(submenu == 3){ //if we are at sub menu 3

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[2]))));

lcd.write(pgm_read_byte(&char_table[7])); //printing assigned room number 3

}

if(submenu == 4){ //if we are at sub menu 4

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[3]))));

lcd.write(pgm_read_byte(&char_table[7])); //printing assigned room number 3

}

if(submenu == 5){ //if we are at sub menu 5

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[4]))));

lcd.write(pgm_read_byte(&char_table[7])); //printing assigned room number 3

}

if(submenu == 6){ //if we are at sub menu 6

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[5]))));

lcd.write(pgm_read_byte(&char_table[7])); //printing assigned room number 3

}

if(submenu == 7){ //if we are at sub menu 7

lcd.clear();

//retrieve and print second sub menu point

lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(submenu_table[6]))));

lcd.write(pgm_read_byte(&char_table[7])); //printing assigned room number 3

}

}

if(subButton == btnSelect){ //if we pressed btnSelect

if(submenu == 1){ //and submenu is 1

//call the function get_delay() to change the setting

delayTime[0] = get_delay(9, 3, dBed3);

return;

}

if(submenu == 2){ //and sub menu is 2

//call the function get_offon to change the setting

room3MActive = get_offon(10, 3, room3MActive);

return;

}

if(submenu == 3){ //and submenu is 3

//call the function get_setTime() to change timer 1

get_setTime(room3OnM[0], room3OnM[1],

room3OffM[0], room3OffM[1], 21);

return;

}

if(submenu == 4){ //and submenu is 4

//call the function get_offon() to change the setting

room301Active = get_offon(11, 3, room301Active);

return;

}

if(submenu == 5){ //and submenu is 5

//call the function get_setTime() to change timer 2

get_setTime(room3On1[0], room3On1[1],

room3Off1[0], room3Off1[1], 22);

return;

}

if(submenu == 6){ //and submenu is 6

//call the function get_offon() to change the setting

room302Active = get_offon(12, 3, room302Active);

return;

}

if(submenu == 7){ //and submenu == 7

//call function get_setTime() to change timer 3

get_setTime(room3On2[0], room3On2[1],

room3Off2[0], room3Off2[1], 23);

return;

}

}

}

} //submenu end

finally, don't forget to add the following to the get_setTime() function:


if(room == 21){

room3OnM[0] = onTimeH;

room3OnM[1] = onTimeM;

room3OffM[0] = offTimeH;

room3OffM[1] = offTimeM;

}

if(room == 22){

room3On1[0] = onTimeH;

room3On1[1] = onTimeM;

room3Off1[0] = offTimeH;

room3Off1[1] = offTimeM;

}

if(room == 23){

room3On2[0] = onTimeH;

room3On2[1] = onTimeM;

room3Off2[0] = offTimeH;

room3Off2[1] = offTimeM;

}