Universal Controller Arduino Sketch

Definitions and Variables

/*

   Tower Control is actually a universal control. 

   It is in a wooden box with a 16x2 blue LCD display with 5 buttons plus reset below it.

   It includes an Arduino UNO and is powered by a 3.7V L-iion 750 MAH battery which has a charger.

   The box has two holes. The one giving access to a USB type B socket is for programming the UNO.

   The other accesses a micro-USB for charging from a 5V, 1A supply.

   The displays and transmit codes are specified in arrays which can be extended.

   John Saunders 5/29/2021

*/

// include the library code:

#include <LiquidCrystal.h>

#include <avr/pgmspace.h>


/*

  The circuit:

   LCD RS pin to digital pin 8

   LCD Enable pin to digital pin 9

   LCD D4 pin to digital pin 4

   LCD D5 pin to digital pin 5

   LCD D6 pin to digital pin 6

   LCD D7 pin to digital pin 7

   LCD Backlight enable pin 10

*/

// initialize the library by associating any needed LCD interface pin

// with the arduino pin number it is connected to

const int rs = 8, en = 9, d4 = 4, d5 = 5, d6 = 6, d7 = 7;

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define BLPin 10                              //Controls the backlight


// Transmit pulse lengths (ms), for reliable 433 MHz transmissions

#define PREPULSE_LEN 20

#define SETUP_LEN 10


#define minVolts 3.5              // Time to charge the battery if less

#define Timeout 560000ul

#define flashON 600


// globals:

const int mids[] = {70, 222, 392, 600, 872, 2000}; //The buttons are on taps of a resistor divider

const char letters[] = {'R', 'U', 'D', 'L', 'S', 'N'};

const float Vcc = 5.119;    // Measured

char keyCode;               // Sent to control a device

byte selRcvr;               // Index to an array of receivers

byte selChnl;               // Index to an array of the actions of the selected receiver


typedef struct {

  char line1[17];           // Second line of the display is action

  char kCode;               // keyCode value

}  chnl_t;


typedef struct {

  char line0[17];           // Top line of the display is receiver

  const chnl_t *chnl;       // Address of the array of actions

  char keyType;             // 'D' insert "O," before keyCode in Transmit, or 'C'

  byte maxChnl;             // max of actions. Used when changing the action

  char dButNext;            // The order of receivers is set by convenience

  char uButPrev;

}  rcvr_t;


// Arays of actions are PROGMEM or they would exceed available RAM


const chnl_t Tower[] PROGMEM = {

  { "Tower Lights off", 'm'}, {"Color LEDs off ", 'k'}, {"  PWD LEDs off  ", 'o'}, 

  {" PWD LEDs on ", 'n'}, {"Color LEDs on", 'j'},{ "Tower Lights on", 'l'}

};

const chnl_t Gazebo[] PROGMEM = {

  {"Owl Fountain off", '0'}, {"Owl Fountain on ", '1'}, {"Boy Fountain off", '2'}, 

  {"Boy Fountain on ", '3'}, {"String light off", '6'},  {"String lights on", '7'}, 

  {" Strip light off", '4'}, {"Strip lights on ", '5'} , {" Vegas Sign off ", 'V'}, 

  {" Vegas Sign on  ", 'L'} };


const chnl_t Garden[] PROGMEM = {

  {"  Girl pump on  ", 'j'}, {"  Girl pump off ", 'T'}, {"   Clear AC on  ", 'm'}, 

  {"  Clear AC off  ", 'J'}, {" Clear Tip on   ", 'X'} ,

  {" Clear Tip off  ", 'N'}, {"  Clear Ring on ", 'd'}, {" Clear Ring off ", 'o'}

};

const chnl_t RLD[] PROGMEM = {

  {"  Display Date  ", 'K'}, {"  Display Time  ", 'L'}, {"Ext Temperature ", 'M'}, 

  {"  Ext Humidity  ", 'N'}, {"  Solar Current ", 'O'}, {"  Battery Volts ", 'P'}

};

const chnl_t LRW[] PROGMEM = {

  {"   Speaker off  ", 'U'}, {"Ceiling Lamp off", 'G'}, {"Ceiling Light on", 'R'}, 

  {"   Speaker on   ", 'S'}, {"Floor light off ", 'f'},  {" Floor light on ", 'b'}, 

  {" Shelf light off", 'F'}, {" Shelf lights on", 'E'} , {"  Pineapple off ", '8'}, 

  {"  Pineapple on  ", 'K'},

};

const chnl_t Garage[] PROGMEM = {

  {" Pole Light off ", 'k'}, {"  Pole Light on ", 'i'}, {"   Flower off   ", 'c'}, 

  {"   Flower on    ", 'a'}, {"Cube Red LED on ", 'A'}, {"Cube Blue LED on", 'e'}, 

  {"Cube Gren LED on", 'D'}, {"Cube Plays BaBa ", 'B'}, {"Cube Plays Chime", 'Q'}, 

  {"Cube PlaysSiren ", 'h'},  {"Cube All LED off", 'H'}

};

const chnl_t RGB[] PROGMEM = {

  {"  Temperatures  ", 'C'}, {"   Humidities   ", 'D'}, {"Garage Temp&Fan ", 'A'}, 

  {"Baro&Solar Curr ", 'E'}, {"    Voltages   ", 'B'},  {" Night Disp;ay  ", 'F'}

};

const chnl_t LRE[] PROGMEM = {

  {"Hanging Light on", 'I'}, {"Hanging Lamp off", 'C'},  {"    Eggs off    ", '9'}, 

  {"     Eggs on    ", 'd'}, {"Circle LEDs off ", 'l'},  {" Circle LEDs on ", 'P'}, 

  {" Missions off   ", 'g'}, {" Missions on    ", 'M'}, {"  Flashing off  ", 'W'}, 

  {"  Flashing on   ", 'Y'},

};      //I = uc eye, l = lc ell

const chnl_t Atmosphere[] PROGMEM = {

  {"Room Temperature", 'a'}, {"  Room Humidity ", 'b'}, {"Ext Temperature ", 'c'}, 

  {"  Ext Humidity  ", 'd'}, {" Baro Pressure ", 'f'}, {"  Solar Current ", 'e'},{"  Gas Quality   ",'g'}

};

// Array of receivers. Some use multiple hardware devices


const  rcvr_t rcvrs[] = {

  {" Tower Display", Tower, 'D', 5, 5, 1}, {"Red LED Display", RLD, 'D', 5, 0, 8}, 

  {"Atmosphere ", Atmosphere, 'D', 6, 6, 7},  {" rgb Display", RGB, 'D', 5, 7, 4}, 

  {"Living Room East", LRE, 'C', 9, 3, 5}, {"Living Room West", LRW, 'C', 9, 4, 0},

  {"Garage & Outside", Garage, 'C', 10, 8, 2}, {"    Gazebo      ", Gazebo, 'C', 9, 2, 3}, 

  {"     Garden     ", Garden, 'C', 7, 1, 6}

};

// For the front porch of the command transmission:

#define PULSE_PORT 2   

Functions

/*********************************************************************

                Transmitter function

   The serial port 'Serial' Tx cannot be made an output

   Serial!.write produces a negative-going output in TX1.

   There is no 'begin' option to invert this, so I had to add a hardware inverter.

   After Serial1.begin() digitalWrite is ignored, so I had to add a hardware 'OR' gate.

   The 433 MHz protocol cannot be done by Tx alone. A  2-transistor circuit is needed.

 **********************************************************************

*/


void transmit(void) {          //if dispCode == 'O' send it and a comma before keyCode

  char charBuffer[] = {"14L1776      "};

  byte index = 7;

  if (rcvrs[selRcvr].keyType == 'D') {

    charBuffer[index++] = 'O';

    charBuffer[index++] = ',';

    charBuffer[index++] = keyCode;

  }

  else {

    charBuffer[index++] = keyCode;

  }

  charBuffer[index++] = 0x0d;

  charBuffer[index++] = 0x0a;

  digitalWrite(PULSE_PORT, HIGH);

  delay(PREPULSE_LEN);

  digitalWrite(PULSE_PORT, LOW);

  delay(SETUP_LEN);

  Serial.write(charBuffer, index);

}


void dispNotice(void) {

  lcd.clear();

  lcd.print("Made in  2020");

  lcd.setCursor(0, 1);

  lcd.print("by John Saunders");

}


void dispWarning(void) {

  lcd.clear();

  lcd.print("Turn me off now");

  lcd.setCursor(0, 1);

  lcd.print("& charge battery");

}


void dispTimeout(void) {

  lcd.clear();

  lcd.print("Timing out...");

  lcd.setCursor(0, 1);

  lcd.print("do something!");

}


float displayBatteryVolts(void) {

  int AnalogVal;

  float bv;

  AnalogVal = analogRead(A1);

  bv = AnalogVal * Vcc / 1024;

  lcd.clear();

  lcd.print(" Tower Control");

  lcd.setCursor(0, 1);

  lcd.print("Battery = ");

  lcd.print(bv, 2);

  lcd.print("V");

  return bv;

}


void dispRcvr(byte inx) {

  lcd.home();

  lcd.print("                ");

  lcd.home();

  lcd.print(rcvrs[inx].line0);

}


char dispChnl(void) {                     // The lower line on the display

  char buf[18];

  for (int index = 0; index < 18; index ++ ) {      // All of chnl_t

    buf[index] = pgm_read_byte_near(&rcvrs[selRcvr].chnl[selChnl].line1[index]);

  };

  lcd.setCursor(0, 1);

  lcd.print("                ");

  lcd.setCursor(0, 1);

  lcd.print(buf);                       // Stops at the null

  return buf[17];                       // Key Code

}


char getLetter(void) {                  // Dissects A0 to get the individual push-buttons

  static bool buttReady = true;         // All buttons must be released before next press.

  int analogVal = analogRead(A0);

  if (analogVal == 1023) {

    buttReady = true;

    return 'N';                         // No button pressed

  }

  else {

    if (buttReady == false) {           // Waiting for the button to be released

      return 'N';

    }

    else {                              // Find the button (R is 0)

      byte index = 0;

      while (mids[index++] < analogVal) ;

      buttReady = false;

      return letters[--index];

    }

  }

Setup and Loop

void setup() {

  float battVolts;

  char letter;

  Serial.begin(2400);

  pinMode(PULSE_PORT, OUTPUT);                //For the 433 MHz pre-pulse

  digitalWrite(PULSE_PORT, LOW);

  // set up the LCD's number of columns and rows:

  lcd.begin(16, 2);

  pinMode(BLPin, OUTPUT);

  digitalWrite(BLPin, HIGH);          // Backlight control;

  dispNotice();

  delay(900);

  battVolts = displayBatteryVolts();

  delay(900);

  selRcvr = 4;

  selChnl = 0;

  letter = getLetter();

  if (letter == 'S') {

    dispNotice();

  }

  if (battVolts < minVolts) {

    dispWarning();

    delay(1000);

  }

  dispRcvr(selRcvr);

  keyCode = dispChnl();

}


void loop() {

  byte newInx;

  int blanking;

  char letter = getLetter();

  if (millis() > Timeout) {

    letter = 'T';

    blanking = millis() & 0xFFFF;

    if (blanking > flashON) {

      digitalWrite(BLPin, LOW);

    }

    else {

      digitalWrite(BLPin, HIGH);

    }

  }

  switch (letter) {

    case 'S':

      transmit();

      break;

    case 'L':

      if (selChnl > 0) {

        selChnl--;

      }

      else {

        selChnl = rcvrs[selRcvr].maxChnl;

      }

      keyCode = dispChnl();

      break;

    case 'R':

      if (selChnl < rcvrs[selRcvr].maxChnl) {

        selChnl++;

      }

      else {

        selChnl = 0;

      }

      keyCode = dispChnl();

      break;

    case 'D':

      newInx = rcvrs[selRcvr].uButPrev;

      dispRcvr(newInx);

      selRcvr = newInx;

      selChnl = 0;

      keyCode = dispChnl();

      break;

    case 'U':

      newInx = rcvrs[selRcvr].dButNext;

      dispRcvr(newInx);

      selRcvr = newInx;

      selChnl = 0;

      keyCode = dispChnl();

      break;

    case 'T':

      dispTimeout();

      break;

  }

  delay(100);

}