#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <IRremote.hpp>
#include <LittleFS.h>
// ================= OLED =================
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// ================= IR =================
#define IR_RX_PIN 2
#define IR_TX_PIN 3
// ================= BOTÕES =================
#define BTN_VOL_UP 17
#define BTN_VOL_DOWN 18
#define BTN_CH_UP 6
#define BTN_CH_DOWN 7
#define BTN_UP 8
#define BTN_DOWN 9
#define BTN_LEFT 10
#define BTN_RIGHT 11
#define BTN_OK 12
#define BTN_POWER 13
#define BTN_EXTRA 16
#define BTN_MODE 14
#define BTN_HOME 19
#define BTN_BACK 20
#define BTN_INPUT 21
#define BTN_OPTION 22
#define MAX_SLOTS 5
#define BTN_COUNT 15 // 15 botões
// Tamanho do Logo do Botafogo
#define LOGO_HEIGHT 64
#define LOGO_WIDTH 128
// Bateria
#define BAT_ADC 26
#define BAT_DIV 2.0
static const unsigned char PROGMEM botafogo[] =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x01, 0xc0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x03, 0xc0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x03, 0xc0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x03, 0xe0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x07, 0xe0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x07, 0xe0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x07, 0xe0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x0f, 0xf0, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x7f, 0xff, 0xff, 0xff, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x7f, 0xff, 0xff, 0xfe, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x3f, 0xff, 0xff, 0xfc, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x0f, 0xff, 0xff, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x07, 0xff, 0xff, 0xe0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x01, 0xff, 0xff, 0xc0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x7f, 0xfe, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x3f, 0xfe, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x7f, 0xfe, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x7f, 0xfe, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfe, 0x7f, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0xf8, 0x1f, 0x80, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x01, 0xf0, 0x0f, 0x80, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x01, 0xe0, 0x03, 0x80, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x80, 0x01, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x03, 0x00, 0x00, 0xc0, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
enum ButtonID {
UP, DOWN,
LEFT, RIGHT,
OK_BTN,
POWER_BTN,
HOME_BTN,
BACK_BTN,
INPUT_BTN,
OPTION_BTN,
EXTRA_BTN,
VOL_UP,
VOL_DOWN,
CH_UP, CH_DOWN
};
const char* buttonNamesTV[BTN_COUNT] = {
"UP","DOWN","LEFT","RIGTH",
"OK","POWER",
"HOME","BACK","INPUT","OPTION", "EXTRA",
"VOL +", "VOL -",
"CH +","CH -"
};
const char* buttonNamesAR[BTN_COUNT] = {
"UP","DOWN","SUPER","FILTER",
"ECO","POWER","MODE","QUIET",
"TIMER","FAN","DIMMER",
"----","----","----","----"
};
const char* buttonNamesSOM[BTN_COUNT] = {
"UP","DOWN","LEFT","RIGTH",
"OK","POWER",
"HOME","RETURN","INPUT","TOOL", "S/W LV",
"VOL +", "VOL -",
"CH +","CH -"
};
int buttonPins[BTN_COUNT] = {
BTN_UP, BTN_DOWN,
BTN_LEFT, BTN_RIGHT,
BTN_OK, BTN_POWER,
BTN_HOME, BTN_BACK, BTN_INPUT, BTN_OPTION,
BTN_EXTRA,
BTN_VOL_UP,
BTN_VOL_DOWN,
BTN_CH_UP, BTN_CH_DOWN,
};
//=============================Bateria==============================
float batteryVoltage = 0;
int batteryPct = 0;
unsigned long lastBatRead = 0;
float readBatteryVoltage() {
long sum = 0;
for (int i = 0; i < 12; i++) {
sum += analogRead(BAT_ADC);
delay(2);
}
float raw = sum / 12.0;
float v_adc = raw * 3.3 / 4095.0;
return v_adc * BAT_DIV;
}
int batteryPercent(float v) {
if (v >= 4.20) return 100;
if (v <= 3.30) return 0;
return (int)((v - 3.30) * 100.0 / (4.20 - 3.30));
}
//=====================================================================
bool lastBtnState[BTN_COUNT];
// ================= IR STORAGE =================
uint32_t irCodes[MAX_SLOTS][BTN_COUNT];
uint8_t slotType[MAX_SLOTS]; // 0=TV, 1=AR, 2=SOM, 255=empty
// ================= MODOS =================
enum Mode {
MODE_IDLE,
MODE_MENU,
MODE_COPY_SLOT,
MODE_COPY_DEVICE,
MODE_COPY_BIND,
MODE_RUN,
MODE_CONFIRM_ERASE
};
Mode currentMode = MODE_IDLE;
int menuIndex = 0;
int currentSlot = 0;
int bindIndex = 0;
int confirmIndex = 0;
int deviceIndex = 0; // 0=TV,1=AR,2=SOM
bool needsRedraw = true;
bool autoRepeatEnabled = false;
unsigned long repeatStart = 0;
int repeatButton = -1;
// ================= EEPROM =================
#define EEPROM_SIZE (MAX_SLOTS * BTN_COUNT * 4 + MAX_SLOTS + 2)
#define EEPROM_MAGIC 0x55AA
// ================= UTIL =================
bool buttonClick(ButtonID id) {
bool pressed = !digitalRead(buttonPins[id]);
bool click = pressed && !lastBtnState[id];
lastBtnState[id] = pressed;
return click;
}
bool modeClick() {
static bool lastState = false;
bool pressed = !digitalRead(BTN_MODE);
bool click = pressed && !lastState;
lastState = pressed;
return click;
}
void clearOLED() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
}
const char* getButtonName(int slot, int idx) {
if (slotType[slot] == 0) return buttonNamesTV[idx];
if (slotType[slot] == 1) return buttonNamesAR[idx];
if (slotType[slot] == 2) return buttonNamesSOM[idx];
return buttonNamesTV[idx];
}
// ================= SLOT =================
bool slotComplete(int slot) {
if (slotType[slot] == 255) return false;
// Se for AR, só checa os primeiros 11 botões
if (slotType[slot] == 1) {
for (int i = 0; i < 11; i++) {
if (irCodes[slot][i] == 0) return false;
}
return true;
}
// TV e SOM checam todos
for (int i = 0; i < BTN_COUNT; i++) {
if (irCodes[slot][i] == 0) return false;
}
return true;
}
bool anySlotCopied() {
for (int s = 0; s < MAX_SLOTS; s++) {
if (slotComplete(s)) return true;
}
return false;
}
bool slotCopied(int slot) {
return slotComplete(slot);
}
void eraseSlot(int slot) {
for (int i = 0; i < BTN_COUNT; i++) irCodes[slot][i] = 0;
slotType[slot] = 255;
}
//=========================Bateria=================================
void updateBattery() {
if (millis() - lastBatRead > 2000) { // atualiza a cada 2s
batteryVoltage = readBatteryVoltage();
batteryPct = batteryPercent(batteryVoltage);
lastBatRead = millis();
}
}
void drawBatteryIcon(int x, int y) {
int w = 20;
int h = 8;
bool low = batteryPct <= 20;
//bool blink = (millis() / 500) % 2; // pisca 2 Hz
//if (low && blink) return; // apaga no pisca
// contorno
display.drawRect(x, y, w, h, SSD1306_BLACK);
display.drawRect(x + w, y + 2, 2, h - 4, SSD1306_BLACK);
// preenchimento
int fill = map(batteryPct, 0, 100, 0, w - 2);
if (fill > 0) {
display.fillRect(x + 1, y + 1, fill, h - 2, SSD1306_BLACK);
}
// % ao lado
display.setTextColor(SSD1306_BLACK);
display.setCursor(x - 15, y);
display.print(batteryPct);
display.print("%");
}
// ================= TELAS =================
void showMenu() {
clearOLED();
const char* items[3] = { "USAR CONTROLE", "COPIAR CONTROLE", "APAGAR CONTROLE" };
display.setCursor(50, 1); // texto no meio
display.fillRect(0, 0, 128, 11, SSD1306_WHITE);//background
display.setTextColor(SSD1306_BLACK);
display.print("Menu");
int yoffset = 15;
for (int i = 0; i < 3; i++) {
int y = yoffset + i * 10;
if (menuIndex == i) display.fillRect(0, y, 128, 10, SSD1306_WHITE);
display.setCursor(2, y + 1);
display.setTextColor(menuIndex == i ? SSD1306_BLACK : SSD1306_WHITE);
display.print(items[i]);
display.setTextColor(SSD1306_WHITE);
}
// Bateria------------------------------
updateBattery();
drawBatteryIcon(100, 2);
display.setCursor(0, 20);
// Bateria------------------------------
display.display();
}
// função showSlot fora do loop
void showSlot() {
clearOLED();
int line = 0;
for (int i = 0; i < MAX_SLOTS; i++) {
if (currentMode == MODE_COPY_SLOT && (menuIndex == 0 || menuIndex == 2)) {
if (!slotCopied(i)) continue; // ignora slots vazios
}
if (i == currentSlot) display.fillRect(0, line * 12, 128, 12, SSD1306_WHITE);
display.setCursor(2, line * 12 + 2);
display.setTextColor(i == currentSlot ? SSD1306_BLACK : SSD1306_WHITE);
display.print("Controle ");
display.print(i + 1);
display.print("= ");
if (slotType[i] != 255) {
if (slotType[i] == 0) display.print(" TV");
if (slotType[i] == 1) display.print(" AR");
if (slotType[i] == 2) display.print(" SOM");
}
if (slotCopied(i)) {
display.print("");
} else {
display.print("[Vazio]");
}
display.setTextColor(SSD1306_WHITE);
line++;
}
display.display();
}
void showDeviceSelect() {
clearOLED();
display.setCursor(0, 0);
display.print("Selecionar aparelho");
//display.setCursor(0, 15);
const char* names[3] = { "TV", "AR", "SOM" };
for (int i = 0; i < 3; i++) {
if (deviceIndex == i) display.fillRect(0, 20 + i * 10, 128, 10, SSD1306_WHITE);
display.setCursor(58, 20 + i * 10 + 1);
display.setTextColor(deviceIndex == i ? SSD1306_BLACK : SSD1306_WHITE);
display.print(names[i]);
display.setTextColor(SSD1306_WHITE);
}
display.display();
}
// ===============Desenha os símbolos =====================
void drawArrowUp(int x, int y) {
display.fillTriangle(x + 10, y + 0, x + 4, y + 10, x + 16, y + 10, SSD1306_WHITE);
}
void drawArrowLeft(int x, int y) {
display.fillTriangle(x + 0, y + 10, x + 10, y + 4, x + 10, y + 16, SSD1306_WHITE);
}
void drawArrowRight(int x, int y) {
display.fillTriangle(x + 20, y + 10, x + 10, y + 4, x + 10, y + 16, SSD1306_WHITE);
}
void drawArrowDown(int x, int y) {
display.fillTriangle(x + 10, y + 20, x + 4, y + 10, x + 16, y + 10, SSD1306_WHITE);
}
void drawPowerSymbol(int x, int y) {
display.drawCircle(x + 7, y + 8, 6, SSD1306_WHITE);
display.drawCircle(x + 7, y + 8, 5, SSD1306_WHITE);
display.drawLine(x + 7, y, x + 7, y + 6, SSD1306_WHITE);
}
void drawInputSymbol(int x, int y) {
// Retângulo (tela)
display.drawRect(x + 6, y + 2, 14, 10, SSD1306_WHITE);
display.drawLine(x, y + 7, x + 13, y + 7, SSD1306_WHITE);
display.drawLine(x + 9, y + 4, x + 13, y + 7, SSD1306_WHITE);
display.drawLine(x + 9, y + 10, x + 13, y + 7, SSD1306_WHITE);
}
void drawOption(int x, int y) {
display.drawLine(x + 6, y + 4, x + 20, y + 4, SSD1306_WHITE);
display.drawLine(x + 6, y + 8, x + 20, y + 8, SSD1306_WHITE);
display.drawLine(x + 6, y + 12, x + 20, y + 12, SSD1306_WHITE);
}
void drawHomeSymbol(int x, int y) {
display.drawLine(x + 3, y + 12, x + 19, y + 12, SSD1306_WHITE);
display.drawLine(x + 3, y + 12, x + 3, y + 6, SSD1306_WHITE);
display.drawLine(x + 19, y + 12, x + 19, y + 6, SSD1306_WHITE);
display.drawLine(x + 3, y + 6, x + 11, y + 2, SSD1306_WHITE);
display.drawLine(x + 19, y + 6, x + 11, y + 2, SSD1306_WHITE);
}
void drawExtraSymbol(int x, int y) {
display.drawCircle(x + 10, y + 8, 6, SSD1306_WHITE);
display.drawCircle(x + 10, y + 8, 2, SSD1306_WHITE);
display.fillRect(x + 9, y + 0, 2, 3, SSD1306_WHITE);
display.fillRect(x + 9, y + 15, 2, 3, SSD1306_WHITE);
display.fillRect(x + 0, y + 7, 3, 2, SSD1306_WHITE);
display.fillRect(x + 17, y + 7, 3, 2, SSD1306_WHITE);
display.fillRect(x + 2, y + 2, 3, 3, SSD1306_WHITE);
display.fillRect(x + 15, y + 2, 3, 3, SSD1306_WHITE);
display.fillRect(x + 2, y + 12, 3, 3, SSD1306_WHITE);
display.fillRect(x + 15, y + 12, 3, 3, SSD1306_WHITE);
}
void drawBack(int x, int y) {
display.drawLine(x + 2, y + 6, x + 14, y + 6, SSD1306_WHITE);
// Desce (linha vertical)
display.drawLine(x + 14, y + 6, x + 14, y + 14, SSD1306_WHITE);
// Volta para a esquerda (linha inferior)
display.drawLine(x + 14, y + 14, x + 6, y + 14, SSD1306_WHITE);
// Ponta da seta apontando para a DIREITA
display.drawLine(x + 6, y + 14, x + 10, y + 12, SSD1306_WHITE);
display.drawLine(x + 6, y + 14, x + 10, y + 16, SSD1306_WHITE);
}
// ============================================================
// =============== ChamaDesenha as setas =====================
void drawButtonLabel(const char* name, int x, int y) {
if (strcmp(name, "UP") == 0) {
drawArrowUp(x, y);
}
else if (strcmp(name, "LEFT") == 0) {
drawArrowLeft(x, y);
}
else if (strcmp(name, "RIGTH") == 0) {
drawArrowRight(x, y);
}
else if (strcmp(name, "DOWN") == 0) {
drawArrowDown(x, y);
}
else if (strcmp(name, "POWER") == 0) {
drawPowerSymbol(x, y);
}
else if (strcmp(name, "INPUT") == 0) {
drawInputSymbol(x, y);
}
else if (strcmp(name, "EXTRA") == 0) {
drawExtraSymbol(x, y);
}
else if (strcmp(name, "HOME") == 0) {
drawHomeSymbol(x, y);
}
else if (strcmp(name, "OPTION") == 0) {
drawOption(x, y);
}
else if (strcmp(name, "BACK") == 0) {
drawBack(x, y);
}
else {
display.setCursor(x, y);
display.print(name);
}
}
// =============== ChamaDesenha as setas =====================
void showBind() {
clearOLED();
display.setCursor(30, 1); // texto no meio
display.setTextColor(SSD1306_BLACK);
display.fillRect(0, 0, 128, 10, SSD1306_WHITE);//background
display.print("Copiando...");
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 15);
display.print("Aponte o controle");
display.setCursor(0, 25);
display.print("para o sensor");
display.setCursor(0, 40);
display.print("Botao: ");
display.setTextSize(2);
//display.setCursor(45, 30);
drawButtonLabel(getButtonName(currentSlot, bindIndex), 45, 40);
display.display();
}
void showRun() {
clearOLED();
display.setCursor(10, 1); // texto no meio
display.setTextColor(SSD1306_BLACK);
display.fillRect(0, 0, 128, 10, SSD1306_WHITE);//background
display.print("Usando");
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 15);
display.print("CONTROLE ");
display.print(currentSlot + 1);
if (slotType[currentSlot] != 255) {
if (slotType[currentSlot] == 0) display.print(" TV");
if (slotType[currentSlot] == 1) display.print(" AR");
if (slotType[currentSlot] == 2) display.print(" SOM");
}
display.setCursor(0, 35);
display.print("Aguardando botao");
// Bateria------------------------------
//display.clearDisplay();
updateBattery();
drawBatteryIcon(100, 1);
display.setCursor(0, 20);
// Bateria------------------------------
display.display();
}
void showRunButton(const char* name) {
clearOLED();
display.setCursor(10, 1); // texto no meio
display.setTextColor(SSD1306_BLACK);
display.fillRect(0, 0, 128, 10, SSD1306_WHITE);//background
display.print("Usando");
// Bateria------------------------------
//display.clearDisplay();
updateBattery();
drawBatteryIcon(100, 1);
display.setCursor(0, 20);
// Bateria------------------------------
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 15);
display.print("CONTROLE ");
display.print(currentSlot + 1);
if (slotType[currentSlot] != 255) {
if (slotType[currentSlot] == 0) display.print(" TV");
if (slotType[currentSlot] == 1) display.print(" AR");
if (slotType[currentSlot] == 2) display.print(" SOM");
}
display.setCursor(0, 35);
display.print("Botao: ");
display.setCursor(50, 35);
display.setTextSize(2);
drawButtonLabel(name, 50, 30);
display.display();
}
void showConfirmErase() {
clearOLED();
display.setCursor(15, 10);
display.print("APAGAR CONTROLE?");
display.setCursor(27, 25);
display.print("CONTROLE ");
if (slotType[currentSlot] != 255) {
if (slotType[currentSlot] == 0) display.print(" TV ");
if (slotType[currentSlot] == 1) display.print(" AR ");
if (slotType[currentSlot] == 2) display.print(" SOM ");
}
display.print(currentSlot + 1);
if (confirmIndex == 0) display.fillRect(10, 45, 40, 12, SSD1306_WHITE);
if (confirmIndex == 1) display.fillRect(70, 45, 40, 12, SSD1306_WHITE);
display.setCursor(18, 47);
display.setTextColor(confirmIndex == 0 ? SSD1306_BLACK : SSD1306_WHITE);
display.print("NAO");
display.setCursor(80, 47);
display.setTextColor(confirmIndex == 1 ? SSD1306_BLACK : SSD1306_WHITE);
display.print("SIM");
display.setTextColor(SSD1306_WHITE);
display.display();
}
void showNoSlot() {
clearOLED();
animacaoNenhumCopiado();
display.display();
}
void botafogo1() {
clearOLED();
// Logo Botafogo
display.drawBitmap(
(display.width() - LOGO_WIDTH ) / 2,
(display.height() - LOGO_HEIGHT) / 2,
botafogo, LOGO_WIDTH, LOGO_HEIGHT, 1);
// Exibe no display
display.display();
delay(1500);
}
//====================== Animação Inicio ===================================================================
void drawPower(int x, int y, int r) {
display.drawCircle(x, y, r, SSD1306_WHITE);
display.drawLine(x, y - r - 2, x, y - 4, SSD1306_WHITE);
}
void animacaoLigando() {
for (int r = 4; r <= 12; r += 2) {
display.clearDisplay();
drawPower(64, 32, r);
display.display();
delay(120);
}
// pequeno hold final
delay(200);
}
//==========================================================================================================
//====================== Animação Copiado ==================================================================
void drawRemote(int x, int y) {
display.drawRect(x, y, 12, 24, SSD1306_WHITE);
display.fillCircle(x + 6, y + 8, 1, SSD1306_WHITE);
display.fillCircle(x + 6, y + 14, 1, SSD1306_WHITE);
}
void drawWaves(int x, int y, int step) {
if (step >= 1) {
display.drawLine(x, y - 4, x, y + 4, SSD1306_WHITE);
}
if (step >= 2) {
display.drawLine(x + 4, y - 6, x + 4, y + 6, SSD1306_WHITE);
}
if (step >= 3) {
display.drawLine(x + 8, y - 8, x + 8, y + 8, SSD1306_WHITE);
}
if (step >= 4) {
display.drawLine(x + 12, y - 10, x + 12, y + 10, SSD1306_WHITE);
}
}
void drawCheck(int x, int y) {
// perna curta
display.drawLine(x, y, x + 4, y + 4, SSD1306_WHITE);
display.drawLine(x + 1, y, x + 5, y + 4, SSD1306_WHITE);
// perna longa
display.drawLine(x + 4, y + 4, x + 12, y - 4, SSD1306_WHITE);
display.drawLine(x + 5, y + 4, x + 13, y - 4, SSD1306_WHITE);
}
void animateCopyDetailed() {
for (int i = 0; i <= 4; i++) {
display.clearDisplay();
// controle
drawRemote(20, 20);
// ondas IR
drawWaves(40, 32, i);
display.display();
delay(150);
}
// tela final
display.clearDisplay();
drawRemote(20, 20);
drawWaves(40, 32, 4);
drawCheck(90, 30);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(90, 42);
display.print("OK");
display.setCursor(15, 1);
display.print("Controle copiado");
display.display();
delay(800);
}
//==================================================================================================
//================================Animação Não copiado ============================================
void drawWarning(int x, int y) {
// triângulo
display.drawLine(x + 9, y, x, y + 16, SSD1306_WHITE);
display.drawLine(x + 9, y, x + 18, y + 16, SSD1306_WHITE);
display.drawLine(x, y + 16, x + 18, y + 16, SSD1306_WHITE);
// exclamação
display.drawLine(x + 9, y + 5, x + 9, y + 11, SSD1306_WHITE);
display.drawPixel(x + 9, y + 14, SSD1306_WHITE);
}
void animacaoNenhumCopiado() {
for (int i = 0; i < 2; i++) {
display.clearDisplay();
drawWarning(53, 15);
display.display();
delay(250);
display.clearDisplay();
display.display();
delay(150);
}
display.clearDisplay();
drawWarning(53, 15);
display.setCursor(20, 42);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print("Nenhum controle ");
display.setCursor(40, 52);
display.setTextSize(1.8);
display.setTextColor(SSD1306_WHITE);
display.print("COPIADO");
display.setCursor(40, 1); // texto no meio
display.fillRect(0, 0, 128, 10, SSD1306_WHITE);//background
display.setTextColor(SSD1306_BLACK);
display.print("ATENCAO!");
display.display();
}
//=================================================================================================
void showCopied() { // Chama a animação de copiado
clearOLED();
animateCopyDetailed();
}
// ================= LittleFS =================
void fsLoad() {
if (!LittleFS.exists("/slots.bin")) return;
File f = LittleFS.open("/slots.bin", "r");
if (!f) return;
f.read((uint8_t*)slotType, sizeof(slotType));
f.read((uint8_t*)irCodes, sizeof(irCodes));
f.close();
}
void fsSave() {
File f = LittleFS.open("/slots.bin", "w");
if (!f) return;
f.write((uint8_t*)slotType, sizeof(slotType));
f.write((uint8_t*)irCodes, sizeof(irCodes));
f.close();
}
// ================= SETUP =================
void setup() {
Serial1.begin(115200);
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
botafogo1();
IrReceiver.begin(IR_RX_PIN, ENABLE_LED_FEEDBACK);
IrSender.begin(IR_TX_PIN);
if (!LittleFS.begin()) {
Serial.println("Erro ao montar LittleFS");
}
for (int i = 0; i < BTN_COUNT; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
lastBtnState[i] = false;
}
pinMode(BTN_MODE, INPUT_PULLUP);
// inicializa slots como vazios
for (int s = 0; s < MAX_SLOTS; s++) {
slotType[s] = 255;
for (int b = 0; b < BTN_COUNT; b++) irCodes[s][b] = 0;
}
fsLoad();
for (int i = 0; i < MAX_SLOTS; i++) {
if (slotType[i] != 0 && slotType[i] != 1 && slotType[i] != 2) slotType[i] = 255;
}
Serial1.println("Sistema iniciado");
currentMode = MODE_MENU;
needsRedraw = true;
}
// ================= LOOP =================
void loop() {
if (modeClick()) {
currentMode = MODE_MENU;
menuIndex = 0;
needsRedraw = true;
}
if (currentMode == MODE_MENU) {
if (buttonClick(UP)) menuIndex = (menuIndex + 2) % 3, needsRedraw = true;
if (buttonClick(DOWN)) menuIndex = (menuIndex + 1) % 3, needsRedraw = true;
if (needsRedraw) {
showMenu();
needsRedraw = false;
}
if (buttonClick(OK_BTN)) {
if ((menuIndex == 0 || menuIndex == 2) && !anySlotCopied()) {
showNoSlot();
delay(1000);
needsRedraw = true;
} else {
if (menuIndex == 0) { // Usar Controle
for (int i = 0; i < MAX_SLOTS; i++) {
if (!slotCopied(currentSlot)) {
for (int i = 0; i < MAX_SLOTS; i++) {
int s = (currentSlot + 1 + i) % MAX_SLOTS;
if (slotCopied(s)) {
currentSlot = s;
break;
}
}
}
}
if (buttonClick(UP)) {
do {
currentSlot = (currentSlot + MAX_SLOTS - 1) % MAX_SLOTS;
} while (menuIndex == 0 && !slotCopied(currentSlot)); // ignora vazios
needsRedraw = true;
}
if (buttonClick(DOWN)) {
do {
currentSlot = (currentSlot + 1) % MAX_SLOTS;
} while (menuIndex == 0 && !slotCopied(currentSlot)); // ignora vazios
needsRedraw = true;
}
} else {
currentSlot = 0;
}
currentMode = MODE_COPY_SLOT;
needsRedraw = true;
}
delay(200);
}
}
if (currentMode == MODE_COPY_SLOT) {
// SE FOR APAGAR SLOT, MOSTRA SOMENTE OS COPIADOS
// USAR CONTROLE E APAGAR SLOT: só slots copiados
if (menuIndex == 0 || menuIndex == 2) {
if (!slotCopied(currentSlot)) {
for (int i = 0; i < MAX_SLOTS; i++) {
int s = (currentSlot + 1 + i) % MAX_SLOTS;
if (slotCopied(s)) {
currentSlot = s;
break;
}
}
}
}
if (buttonClick(UP)) {
do {
currentSlot = (currentSlot + MAX_SLOTS - 1) % MAX_SLOTS;
} while (menuIndex == 2 && !slotCopied(currentSlot)); // ignora vazios
needsRedraw = true;
}
if (buttonClick(DOWN)) {
do {
currentSlot = (currentSlot + 1) % MAX_SLOTS;
} while ((menuIndex == 0 || menuIndex == 2) && !slotCopied(currentSlot));
// ignora vazios
needsRedraw = true;
}
if (needsRedraw) {
showSlot();
needsRedraw = false;
}
if (buttonClick(OK_BTN)) {
if (menuIndex == 0) {
currentMode = MODE_RUN;
needsRedraw = true;
} else if (menuIndex == 1) {
currentMode = MODE_COPY_DEVICE;
deviceIndex = 0;
needsRedraw = true;
} else {
currentMode = MODE_CONFIRM_ERASE;
confirmIndex = 0;
needsRedraw = true;
}
delay(200);
}
}
if (currentMode == MODE_CONFIRM_ERASE) {
if (buttonClick(LEFT) || buttonClick(RIGHT)) {
confirmIndex = !confirmIndex;
needsRedraw = true;
}
if (needsRedraw) {
showConfirmErase();
needsRedraw = false;
}
if (buttonClick(OK_BTN)) {
if (confirmIndex == 1) {
eraseSlot(currentSlot);
fsSave();
// ---------- PISCA A TELA (SEM TEXTO) ----------
for (int i = 0; i < 3; i++) {
display.clearDisplay();
display.display();
delay(200);
display.clearDisplay();
display.fillRect(0, 0, 128, 64, SSD1306_WHITE);
display.display();
delay(200);
}
// MOSTRA A MENSAGEM APÓS PISCAR
display.clearDisplay();
display.setCursor(15, 30);
display.print("Controle apagado");
display.display();
delay(2000);
}
currentMode = MODE_MENU;
needsRedraw = true;
delay(300);
}
}
if (currentMode == MODE_COPY_DEVICE) {
if (buttonClick(UP)) deviceIndex = (deviceIndex + 2) % 3, needsRedraw = true;
if (buttonClick(DOWN)) deviceIndex = (deviceIndex + 1) % 3, needsRedraw = true;
if (needsRedraw) {
showDeviceSelect();
needsRedraw = false;
}
if (buttonClick(OK_BTN)) {
slotType[currentSlot] = deviceIndex;
bindIndex = 0;
currentMode = MODE_COPY_BIND;
needsRedraw = true;
delay(200);
}
}
if (currentMode == MODE_COPY_BIND) {
if (needsRedraw) {
showBind();
needsRedraw = false;
}
if (IrReceiver.decode()) {
irCodes[currentSlot][bindIndex] = IrReceiver.decodedIRData.decodedRawData;
Serial1.print("BIND Slot ");
Serial1.print(currentSlot + 1);
Serial1.print(" Botao ");
Serial1.print(getButtonName(currentSlot, bindIndex));
Serial1.print(" RAW: 0x");
Serial1.println(irCodes[currentSlot][bindIndex], HEX);
IrReceiver.resume();
bindIndex++;
//IGNORA OS 4 BOTÕES EXTRAS NO MODO AR
if (slotType[currentSlot] == 1 && bindIndex >= 11) {
bindIndex = BTN_COUNT;
}
if (bindIndex >= BTN_COUNT) {
fsSave();
showCopied();
delay(200); // aqui agora
currentMode = MODE_MENU;
needsRedraw = true;
} else {
needsRedraw = true;
}
delay(300);
}
}
if (currentMode == MODE_RUN) {
if (needsRedraw) {
showRun();
needsRedraw = false;
}
for (int i = 0; i < BTN_COUNT; i++) {
if (slotType[currentSlot] == 1 && i >= 11) continue; // AR ignora os 4 extras
if (buttonClick((ButtonID)i)) {
repeatButton = i;
repeatStart = millis();
Serial1.print("SEND Slot ");
Serial1.print(currentSlot + 1);
Serial1.print(" Botao ");
Serial1.print(getButtonName(currentSlot, i));
Serial1.print(" RAW: 0x");
Serial1.println(irCodes[currentSlot][i], HEX);
IrSender.sendNECMSB(irCodes[currentSlot][i], 32);
showRunButton(getButtonName(currentSlot, i));
}
}
// REPETIÇÃO AUTOMÁTICA
if (repeatButton >= 0) {
if (!digitalRead(buttonPins[repeatButton])) {
if (millis() - repeatStart > 200) {
repeatStart = millis();
Serial1.print("SEND (hold) Slot ");
Serial1.print(currentSlot + 1);
Serial1.print(" Botao ");
Serial1.print(getButtonName(currentSlot, repeatButton));
Serial1.print(" RAW: 0x");
Serial1.println(irCodes[currentSlot][repeatButton], HEX);
IrSender.sendNECMSB(irCodes[currentSlot][repeatButton], 32);
}
} else {
repeatButton = -1;
}
}
}
}