/*
keypad-Control.ino
This is a home automation controller using on-off modilation with RS-232 transmitter at 433 MHz
The display is Jameco SIC2004A_BLWIT. It came with an I2C adapter already attached.
The pocessor is a Pololu A-Star mini 32u4 LV
This has an efficient DC-DC converter to boost the battery voltage to 5V.
It has a STANDBY pin, high to turn off the convrter.
This is operated by a battery of between 2.7 and 11.8V. At 4.5V the current draw is 150 MA
During standby this drops to 45 microamperes. Power is 3 AA batteries.
Turn-on uses a ball-type tilt sensor. Active on nearly upside-down.
John Saunders 6/24/2024
*/
#include <Wire.h>
#include <hd44780.h> // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
#include <EEPROM.h>
hd44780_I2Cexp lcd(0x27 );
//Pins 0,1 are Serial1, 2,3 are I2C
#define COL_1_PIN 4 //Direct connections through the 4x4 matrixed keypad
#define COL_2_PIN 5
#define COL_3_PIN 6
#define COL_4_PIN 7
#define ROW_1_PIN 8
#define ROW_2_PIN 9
#define ROW_3_PIN 10
#define ROW_4_PIN 11
#define PULSE_PIN 12 //For the transmitter unlock pre-pulse
#define KEEP_ALIVE_PIN 13 //To maintain power after the tlt sensor is back off
#define LCD_COLS 20
#define LCD_ROWS 4
#define LONG_TIMEOUT 600 //20 count/sec after the last key-press before auto-turnoff
#define SHORT_TIMEOUT 160
const int colPins[4] = {COL_1_PIN, COL_2_PIN, COL_3_PIN, COL_4_PIN};
const int rowPins[4] = {ROW_1_PIN, ROW_2_PIN, ROW_3_PIN, ROW_4_PIN};
uint8_t alphaKey; //Set by the A-D keyys
uint8_t decKey; //Set by the numeric keys
uint8_t numItems; //Not used
bool tic; //True if a key has ben depressed
uint8_t alphaKeySave; //Last transmitted value for
uint8_t decKeySave; //same display next time started
int loopCount; //For auto power-down
int itemList[40] = //To make the user experirnce independent of the EEPROM order
{
20, 0, 27, 21, 22, 18, 19, 3, 1, 2,
11, 10, 4, 5, 6, 7, 8, 9, 35, 36,
34, 12, 13, 14, 15, 16, 17, 11, 10, 21,
0, 24, 25, 26, 28, 29, 30, 31, 32, 33
};
//These 4 structs/arrays must be identical to those in EEPROMwrite.ino
struct stats_t { //The first 5 are read only
int lastAddr; //Not used
int areasStart; //EEPROM address of area (top line) table
int line2Start; //EEPROM address of line 2 (third line) table
uint8_t numItems; //Not used
uint8_t decKey = 0; //These two are written by Keypad-Control
uint8_t alphaKey = 0; //before power off,and read back at setup().
};
stats_t stats;
typedef char area_t[18];
area_t areaLine; //Wil be filled from the EEPROM
struct dispLine_t {
char offCode;
char onCode;
byte areaInx;
byte lineTwoInx;
char itemName[16];
};
dispLine_t lineOne; //Wil be filled from the EEPROM
typedef char lineTwo_t[18];
lineTwo_t lineTwo; //Wil be filled from the EEPROM
struct key_t {
int col_inx;
int row_inx;
char keyChar; //Not used
};
const key_t keyVals[16] = { //The index of this table is the result
{1, 3, '0'}, {0, 0, '1'}, {1, 0, '2'}, {2, 0, '3'}, {0, 1, '4'},
{1, 1, '5'}, {2, 1, '6'}, {0, 2, '7'}, {1, 2, '8'}, {2, 2, '9'},
{3, 0, 'A'}, {3, 1, 'B'}, {3, 2, 'C'}, {3, 3, 'D'}, {0, 3, '*'}, {2, 3, '#'},
};
//There are 38 single character commands, plus 2-character commands with the first=O
void transmit(char actionCode, bool actionType = true) { // true is 2-character)
int spacing = 7;
char sendBuff[] = "14L1776 ";
if (actionType) {
sendBuff[spacing++] = 'O';
sendBuff[spacing++] = ',';
}
sendBuff[spacing++] = actionCode;
sendBuff[spacing++] = 13;
sendBuff[spacing++] = 10;
sendBuff[spacing] = 0;
digitalWrite(PULSE_PIN, HIGH);
delay(20);
digitalWrite(PULSE_PIN, LOW);
delay(10);
Serial1.write(sendBuff);
alphaKeySave = alphaKey; //For the bext session
decKeySave = decKey;
lcd.setCursor(0, 3); //Display on the bottom line
lcd.print(" Code ");
if (actionType) {
lcd.print('O');
}
lcd.print(actionCode);
lcd.print(" sent ");
delay(300); //To avoid repeats, and the some receivers needs this
}
void shutDown(void) {
stats.decKey = decKeySave;
stats.alphaKey = alphaKeySave;
EEPROM.put(1010, stats);
loopCount = -1;
digitalWrite(KEEP_ALIVE_PIN, LOW);
}
int getKey(void) { //130 microsec for no key pressed
int keyIndex = 16; //Default output if no key depressed
int selRow = 4;
int selCol = 4;
int i, j;
for (i = 0; i < 4; i++) {
digitalWrite(colPins[i], LOW); //Poll thre columns
for (j = 0; j < 4; j++) {
if (digitalRead(rowPins[j]) == LOW) { // A key press goes to 2.4 milliseconds
selRow = j;
selCol = i;
break; //When a key press is detected
}
}
digitalWrite(colPins[i], HIGH); //Stop polling is a key is depressed
if (selRow < 4) break;
}
if ((selRow < 4) && (selCol < 4)) { //Find the match if a key press detected
for (int k = 0; k < 16; k++) {
if ((selRow == keyVals[k].row_inx) && (selCol == keyVals[k].col_inx)) {
keyIndex = k;
break;
}
}
}
return keyIndex;
}
void getLine(int alpha, int dec) { //stats must be read before.
int inx;
int selInx;
inx = 10 * alpha + dec;
selInx = itemList[inx];
int eepromAddr = sizeof(dispLine_t) * selInx; //Starts at 0
EEPROM.get(eepromAddr, lineOne);
eepromAddr = (sizeof(area_t) * lineOne.areaInx) + stats.areasStart;
EEPROM.get(eepromAddr, areaLine);
eepromAddr = (sizeof(lineTwo_t) * lineOne.lineTwoInx) + stats.line2Start;
EEPROM.get(eepromAddr, lineTwo);
}
void setup() {
int status;
int i;
pinMode(KEEP_ALIVE_PIN, OUTPUT);
digitalWrite(KEEP_ALIVE_PIN, HIGH); //68 milliseconds fron STBY going low
delay(100);
status = lcd.begin(LCD_COLS, LCD_ROWS);
if (status) // non zero status means it was unsuccesful
{
// hd44780 has a fatalError() routine that blinks a led if possible
// begin() failed so blink error code using the onboard LED if possible
hd44780::fatalError(status); // does not return
}
for (i = 0; i < 4; i++) {
pinMode(colPins[i], OUTPUT);
digitalWrite(colPins[i], HIGH);
}
for (i = 0; i < 4; i++) {
pinMode(rowPins[i], INPUT_PULLUP);
}
Serial1.begin(2400);
lcd.home();
lcd.clear();
// prints attribute message
lcd.setCursor(2, 0);
lcd.print("Universal Control");
lcd.setCursor(0, 1);
lcd.print("Made in May 2024 by");
lcd.setCursor(3, 2);
lcd.print("John Saunders");
lcd.setCursor(7, 3);
lcd.print("Age 90");
delay(2000);
EEPROM.get(1010, stats); //Get EEPROM starting addresses
alphaKey = stats.alphaKey;
decKey = stats.decKey;
numItems = stats.numItems; //Not used
loopCount = timeout;
tic = true;
}
void loop() {
int keyInx;
if (loopCount > 0) {
loopCount--;
}
if (loopCount == 0) {
shutDown();
}
/**********************************************************
Get Key Press and read EEPROM
**********************************************************
*/
keyInx = getKey();
if ((keyInx > 9) && (keyInx < 14)) {
alphaKey = keyInx - 10;
tic = true;
}
if ((keyInx >= 0) && (keyInx < 10)) {
decKey = keyInx;
tic = true;
}
if (tic) {
getLine(alphaKey, decKey);
}
/**********************************************************
Display lines 0-2
**********************************************************
*/
if (tic) { //To prevet flickering
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(areaLine);
lcd.setCursor(0, 1);
if (lineOne.areaInx < 3) { //Seiection commands
lcd.print("Show ");
}
else {
lcd.print("Item="); //On-off commands
}
lcd.print(lineOne.itemName);
lcd.setCursor(0, 2);
if (lineOne.areaInx < 3) {
lcd.print("or *=");
}
else {
lcd.print(" ");
}
lcd.print(lineTwo);
}
/**********************************************************
areaInx range ON Action Transmit code
0-1 Selection 2-characters
2 Selection 1-character
3-8 On-Off 1-character
**********************************************************
*/
const char allOff[] = "W9fC8UJ6"; //Lights visable in living room
if (keyInx == 14) { //* Usually OFF
if (lineOne.areaInx < 2) {
switch (lineOne.onCode) { //Special alternate actions
case'n':
shutDown();
break;
case'p': //All living room lghts off
for (int i = 0; i < 8; i++) {
transmit(allOff[i], false);
delay(400);
}
break;
case'F':
transmit('F', true);
default:
transmit(lineOne.onCode, true);
break;
}
}
else { //normal OFF commands
transmit(lineOne.offCode, false);
}
}
if (keyInx == 15) { //# ON or Selection
if (lineOne.areaInx > 1) {
transmit(lineOne.onCode, false);
}
else {
transmit(lineOne.offCode, true);
}
}
if (tic) { //Reset timeout on keypress
loopCount = timeout;
}
tic = false;
delay(50);
}