/*
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
/*********************************************************************
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];
}
}
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);
}