#define EXCLUDE_EXOTIC_PROTOCOLS
#include <Keypad.h>
#include <IRremote.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SEQUENCE_NAME_COLLUMNS 2
#define IR_IN_DATA_PIN 4
#define IR_OUT_DATA_PIN 7
/* ========================= Setup 10 Button Phone Keyboard ========================= */
/* Delay after which a new character will be inserted into the buffer,
* instead of editing the current one
*/
#define KEYBOARD_NEXT_KEY_DELAY 600
/* Stores the character that should be returned by the keyboard as
* well as a flag to signal if the cursor sould advance to the
* next character
*/
struct PhoneKey {
char character;
bool advance;
};
/* Phone keyboard class
* `reset()` method sould be used when done interacting with the keyboard
*/
class PhoneKeyboard {
public:
PhoneKeyboard(unsigned long timeout)
: lastPressTime(0), lastPressed(0), timeout(timeout), keyIndex(0) {}
PhoneKey getCharacter(char pressed) {
PhoneKey ret;
ret.advance = false;
ret.character = 0;
unsigned long now = millis();
if (lastPressed == 0) {
/* If no key was pressed before return the character of the key that was
* pressed
*/
keyIndex = 0;
ret.character = pressed;
} else if (now - lastPressTime > timeout) {
/* If enough time has passed since the last keypress, return the key
* that was pressed and advance the cursor
*/
keyIndex = 0;
ret.character = pressed;
ret.advance = true;
} else if (lastPressed == pressed) {
/* If not enough time has passed, return the next character in the
* sequence corresponding to the key pressed, and do not advance
* the cursor
*/
keyIndex = (keyIndex + 1) % (strlen(keys[pressed - '0']));
ret.character = keys[pressed - '0'][keyIndex];
ret.advance = false;
} else {
/* If the key is different from the last one, return the
* character that was pressed and advance the cursor
*/
keyIndex = 0;
ret.character = pressed;
ret.advance = true;
}
/* Record the pressed key and the press time */
lastPressed = pressed;
lastPressTime = now;
return ret;
}
/* Reset the keyboard to the initial state
* required after done/before interacting
* with it
*/
void reset() {
lastPressTime = 0;
lastPressed = 0;
keyIndex = 0;
}
private:
/* Old phone key sequences */
const char *const keys[10] = {"0", "1", "2ABC", "3DEF", "4GHI",
"5JKL", "6MNO", "7PQRS", "8TUV", "9WXYZ"};
unsigned long lastPressTime;
char lastPressed;
unsigned long timeout;
unsigned int keyIndex;
};
PhoneKeyboard phone = PhoneKeyboard(KEYBOARD_NEXT_KEY_DELAY);
/* ======================================================================= */
/* ========================= Setup Sequence Names ========================= */
/* Number of event sequence names we can store */
#define SEQUENCE_COUNT 4
/* Maximum length of the sequence name */
#define SEQUENCE_MAX_LEN 8
/* Index of the currently selected sequence */
int sequenceIndex = 0;
/* Index to specific character inside the currently selected
* sequence
*/
int sequenceSubIndex = 0;
/* Storage for sequence names */
char sequenceNames[SEQUENCE_COUNT][SEQUENCE_MAX_LEN] = {0};
/* ======================================================================= */
/* ========================= Setup Keypad ========================= */
/* Keypad dimensions */
#define KEYPAD_ROWS 4
#define KEYPAD_COLS 4
/* Keypad character map */
static const char keys[KEYPAD_ROWS][KEYPAD_COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
/* Row and collumn pin connections */
static const byte rowPins[KEYPAD_ROWS] = {12, 11, 10, 9};
static const byte colPins[KEYPAD_COLS] = {8, 7, 6, 5};
/* Keypad initialisation */
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, KEYPAD_ROWS, KEYPAD_COLS);
/* ======================================================================= */
/* ========================= Setup OLED display ========================= */
/* Screen dimensions */
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
/* Display reset pin (shared with Arduino) */
#define OLED_RESET -1
/* I2C Oled Display */
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
/* ======================================================================= */
/* ========================= Setup event storage ========================= */
/* Maximum length of the event sequence
* This was limited by the amount of memory on the Arduino
*/
#define EVENT_LEN 2
/* RemoteEvent struct
* Stores events received by the IR receiver and their timestamps, for use
* later when replaying.
*/
struct RemoteEvent {
RemoteEvent() : rawData(0), delay(0) {}
uint32_t rawData;
unsigned long delay;
};
/* Event action index
* Stores the index to event we are currently recording/playing back
*/
int actionIndex = 0;
/* Storage for the event sequences saved on the SmartRemote */
RemoteEvent events[SEQUENCE_COUNT][EVENT_LEN];
/* Lengths of the event sequences */
int lengths[SEQUENCE_COUNT];
/* ======================================================================= */
/* ========================= SmartRemote State Machine ========================= */
enum State {
/* Select wheter you want to record or play back a sequence */
SELECT_ACTION,
/* Select the slot you want to record the sequence to */
RECORD_SLOT_SELECT,
/* Input the name of the sequence you want to record */
RECORD_NAME,
/* Record the actions from the remote in the sequence */
RECORD_ACTION,
/* Select the slot you want to play back */
PLAYBACK_SLOT_SELECT,
/* Playback in progress */
PLAYBACK_RUN,
};
State state = SELECT_ACTION;
/* ======================================================================= */
void setup() {
Serial.begin(9600);
IrReceiver.begin(IR_IN_DATA_PIN);
keypad.addEventListener(keypadEvent);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for(;;);
}
/* Make sure the sequence names are zeroed out */
for (int i = 0 ; i < SEQUENCE_COUNT; i++) {
sequenceNames[i][0] = 0;
}
/* Setup the display parameters */
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
}
void printSavedCommandsequenceNames() {
display.clearDisplay();
display.setCursor(0, 0);
display.print(F("Select recording:"));
for (int i = 0 ; i < SEQUENCE_COUNT; i++) {
if (i % SEQUENCE_NAME_COLLUMNS == 0) {
display.println();
}
display.print(i);
display.print('.');
display.print(sequenceNames[i]);
for (int j = 0 ; j < SEQUENCE_MAX_LEN - strlen(sequenceNames[i]); j++){
display.print(' ');
}
}
display.display();
}
void printSelectAction() {
display.clearDisplay();
display.setCursor(0, 0);
display.println(F("Choose action:"));
display.println(F("Press \"*\" to record."));
display.println(F("Press \"#\" to play."));
display.display();
}
void printSelectRecordingSlot() {
display.clearDisplay();
display.setCursor(0, 0);
display.print(F("Choose slot. Up to "));
display.println(SEQUENCE_COUNT - 1);
display.display();
}
void printNamingProgress() {
display.clearDisplay();
display.setCursor(0, 0);
display.print(F("Naming slot "));
display.println(sequenceIndex);
display.print(sequenceNames[sequenceIndex]);
for (int i = strlen(sequenceNames[sequenceIndex]); i < SEQUENCE_MAX_LEN - 1; i++) {
display.print('_');
}
display.println();
display.println(F("Press \"*\" to finish."));
display.display();
}
void printRecordingProgress() {
display.clearDisplay();
display.setCursor(0, 0);
display.print(F("Recording actions to slot "));
display.println(sequenceIndex);
for (int i = 0 ; i < EVENT_LEN; i++) {
if (i < actionIndex) {
display.print('X');
} else {
display.print('_');
}
}
display.println();
display.println(F("Press \"*\" to finish."));
display.display();
}
void printPlayback() {
display.clearDisplay();
display.setCursor(0, 0);
display.println("Playing...");
display.display();
}
void loop() {
char key = keypad.getKey();
switch(state) {
case SELECT_ACTION: {
printSelectAction();
} break;
case RECORD_SLOT_SELECT: {
printSelectRecordingSlot();
} break;
case RECORD_NAME: {
printNamingProgress();
} break;
case RECORD_ACTION: {
printRecordingProgress();
/* If we managed to decode someting */
if (IrReceiver.decode()) {
if (IrReceiver.decodeNEC()) {
/* Currently we only support NEC */
IrReceiver.printIRResultShort(&Serial);
if (actionIndex < EVENT_LEN) {
/* Save the raw command in the selected sequence */
events[sequenceIndex][actionIndex].rawData = IrReceiver.decodedIRData.decodedRawData;
events[sequenceIndex][actionIndex].delay = millis();
actionIndex = actionIndex + 1;
lengths[sequenceIndex] = actionIndex;
}
}
IrReceiver.resume();
}
} break;
case PLAYBACK_SLOT_SELECT: {
printSavedCommandsequenceNames();
} break;
case PLAYBACK_RUN: {
printPlayback();
/* Wait for the delay between actions */
if (actionIndex > 0) {
delay(events[sequenceIndex][actionIndex].delay - events[sequenceIndex][actionIndex - 1].delay);
}
/* Debug output */
Serial.println(events[sequenceIndex][actionIndex].rawData, HEX);
actionIndex += 1;
if (actionIndex == lengths[sequenceIndex]) {
/* If we reached the end of the sequence
* go back to selecting the action
*/
state = SELECT_ACTION;
}
} break;
}
}
void keypadEvent(KeypadEvent key){
static KeypadEvent lastKeyPressed = 0;
static unsigned long lastTimePressed = 0;
if (keypad.getState() == PRESSED) {
switch(state) {
case SELECT_ACTION: {
/* Select if you want to record or play back a sequence */
if (key == '*') {
state = RECORD_SLOT_SELECT;
} else if (key == '#') {
state = PLAYBACK_SLOT_SELECT;
}
actionIndex = 0;
} break;
case RECORD_SLOT_SELECT: {
/* Allow recording only on valid slots */
if (key >= '0' && key < ('0' + SEQUENCE_COUNT)) {
state = RECORD_NAME;
sequenceIndex = key - '0';
sequenceSubIndex = 0;
}
} break;
case RECORD_NAME : {
/* Name input done, go to recording action */
if (key == '*') {
state = RECORD_ACTION;
break;
}
/* If valid key pressed fetch the character from the
* phone keyboard
*/
if (key >= '0' && key <= '9') {
PhoneKey pkey = phone.getCharacter(key);
if (pkey.character == 0) {
break;
}
if (pkey.advance && (sequenceSubIndex + 1) < (SEQUENCE_MAX_LEN - 1)) {
sequenceSubIndex += 1;
}
sequenceNames[sequenceIndex][sequenceSubIndex] = pkey.character;
}
} break;
case RECORD_ACTION: {
/* End recording */
if (key == '*') {
phone.reset();
state = SELECT_ACTION;
sequenceSubIndex = 0;
actionIndex = 0;
}
} break;
case PLAYBACK_SLOT_SELECT: {
/* Select the sequence you want to play back */
if (key >= '0' && key < ('0' + SEQUENCE_COUNT)) {
sequenceIndex = key - '0';
if (sequenceNames[sequenceIndex][0] != 0) {
state = PLAYBACK_RUN;
actionIndex = 0;
} else {
state = SELECT_ACTION;
}
}
} break;
}
}
}