Not a member of GistPad yet?
Sign Up,
it unlocks many cool features!
- // ESP32 core 3.x defines these names in pins_arduino.h.
- // pin_config.h uses the same names for board constants.
- #ifdef SDMMC_CLK
- #undef SDMMC_CLK
- #endif
- #ifdef SDMMC_CMD
- #undef SDMMC_CMD
- #endif
- #include "pin_config.h"
- #include <Arduino.h>
- #include <Wire.h>
- #include <math.h>
- #include <memory>
- #include "Arduino_DriveBus_Library.h"
- #include "Arduino_GFX_Library.h"
- #include "SensorQMI8658.hpp"
- #include "SensorPCF85063.hpp"
- // ============================================================
- // POWER / BATTERY
- // ============================================================
- #include "XPowersLib.h"
- XPowersPMU power;
- bool powerReady = false;
- bool batteryConnected = false;
- bool batteryCharging = false;
- int batteryPercent = 0;
- int batteryVoltage = 0;
- int vbusVoltage = 0;
- int systemVoltage = 0;
- unsigned long lastBatteryUpdate = 0;
- const unsigned long BATTERY_UPDATE_INTERVAL = 10000;
- // ============================================================
- // DIAGNOSTICS
- // ============================================================
- unsigned long sensorSampleCount = 0;
- unsigned long lastHeartbeat = 0;
- const unsigned long HEARTBEAT_INTERVAL = 10000;
- // ============================================================
- // AUDIO
- // ============================================================
- #include "ESP_I2S.h"
- #include "esp_check.h"
- #include "es8311.h"
- I2SClass i2s;
- #define AUDIO_SAMPLE_RATE 16000
- #define AUDIO_VOLUME 85
- #define AUDIO_I2C_NUM 0
- #define AUDIO_SCLK 41
- #define AUDIO_LRCK 45
- #define AUDIO_DSDIN 40
- #define AUDIO_ASDOUT 42
- #define AUDIO_MCLK 16
- #define AUDIO_PA_CTRL 46
- // ============================================================
- // DISPLAY
- // ============================================================
- Arduino_DataBus *bus = new Arduino_ESP32QSPI(
- LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3
- );
- Arduino_GFX *gfx = new Arduino_CO5300(
- bus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 22, 0, 0, 0
- );
- // ============================================================
- // LCD POWER RAIL (DSI_PWR_EN)
- // ============================================================
- #ifndef LCD_EN
- #define LCD_EN 13
- #endif
- const int LCD_EN_PIN = LCD_EN;
- const int LCD_CS_PIN = LCD_CS;
- // ============================================================
- // SCREEN ON / OFF (HARDWARE POWER GATE)
- // ============================================================
- bool screenIsOn = true;
- unsigned long screenOnTime = 0;
- // Prevent accidental redraws while the panel is powered down.
- bool screenWakeRequested = false;
- const unsigned long HOME_SCREEN_TIMEOUT = 15000;
- const unsigned long IM_SCREEN_TIMEOUT = 15000;
- const unsigned long GAME_SCREEN_TIMEOUT = 15000;
- const unsigned long CALC_SCREEN_TIMEOUT = 15000;
- // ============================================================
- // TOUCH
- // ============================================================
- std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus =
- std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire);
- void drawHomeScreen();
- void processHomeScreenTouch(int x, int y);
- void startCalc();
- void drawCalc();
- void processCalcTouch(int x, int y);
- void screenOn();
- void screenOff();
- void screenHardwareOn();
- void screenHardwareOff();
- void screenWake();
- void screenKeepOn();
- void screenAutoOffCheck();
- void drawBatteryIcon();
- void Arduino_IIC_Touch_Interrupt(void);
- std::unique_ptr<Arduino_IIC> FT3168(
- new Arduino_FT3x68(
- IIC_Bus,
- FT3168_DEVICE_ADDRESS,
- DRIVEBUS_DEFAULT_VALUE,
- TP_INT,
- Arduino_IIC_Touch_Interrupt
- )
- );
- void Arduino_IIC_Touch_Interrupt(void)
- {
- FT3168->IIC_Interrupt_Flag = true;
- }
- // ============================================================
- // QMI8658
- // ============================================================
- SensorQMI8658 qmi;
- IMUdata acc;
- // ============================================================
- // RTC
- // ============================================================
- SensorPCF85063 rtc;
- // ============================================================
- // PHYSICAL BUTTON
- // ============================================================
- #define RESET_BUTTON_PIN 0
- #define BUTTON_PRESSED LOW
- const unsigned long BUTTON_DEBOUNCE = 50;
- const unsigned long BUTTON_DOUBLE_PRESS_WINDOW = 700;
- bool lastButtonReading = HIGH;
- bool buttonState = HIGH;
- unsigned long lastButtonChange = 0;
- unsigned long firstButtonPressTime = 0;
- bool waitingForButtonSecondPress = false;
- // ============================================================
- // COLORS
- // ============================================================
- #define COLOR_BG RGB565_BLACK
- #define COLOR_GREEN RGB565_GREEN
- #define COLOR_WHITE RGB565_WHITE
- #define COLOR_CYAN RGB565_CYAN
- #define COLOR_YELLOW RGB565_YELLOW
- #define COLOR_RED RGB565_RED
- #define COLOR_ORANGE RGB565_ORANGE
- #define COLOR_BLUE RGB565_BLUE
- #define COLOR_PURPLE RGB565_MAGENTA
- // ============================================================
- // SEIZURE DETECTION
- // ============================================================
- const unsigned long SENSOR_SAMPLE_INTERVAL = 1;
- const float MOTION_DELTA_THRESHOLD = 0.12f;
- const int MOTION_COUNT_THRESHOLD = 35;
- const unsigned long SEIZURE_WINDOW = 1000;
- const unsigned long SEIZURE_CONFIRM_TIME = 700;
- const unsigned long ALERT_DURATION = 10000;
- const unsigned long ALERT_COOLDOWN = 5000;
- // ============================================================
- // SEIZURE STATE
- // ============================================================
- enum MonitorState { MONITORING, POSSIBLE_SEIZURE, ALERT, COOLDOWN };
- MonitorState monitorState = MONITORING;
- unsigned long possibleStart = 0;
- unsigned long alertStart = 0;
- unsigned long cooldownStart = 0;
- // ============================================================
- // MOVEMENT ANALYSIS
- // ============================================================
- float total_acc = 0.0f;
- float previous_acc = 1.0f;
- float motion_delta = 0.0f;
- unsigned long motionWindowStart = 0;
- int motionCount = 0;
- // ============================================================
- // DISPLAY TIMING
- // ============================================================
- unsigned long lastDisplay = 0;
- unsigned long lastClockDisplay = 0;
- unsigned long lastSerial = 0;
- unsigned long lastAlertScreen = 0;
- int lastDisplayedHour = -1;
- int lastDisplayedMinute = -1;
- // ============================================================
- // TOUCH / GAME
- // ============================================================
- enum GameState { GAME_NONE, GAME_QUESTION, GAME_REVEAL, GAME_RESULT };
- GameState gameState = GAME_NONE;
- bool gameActive = false;
- unsigned long lastTouchTime = 0;
- unsigned long firstTapTime = 0;
- bool waitingForSecondTap = false;
- const unsigned long DOUBLE_TAP_WINDOW = 600;
- const unsigned long TOUCH_DEBOUNCE = 100;
- int touchX = 0;
- int touchY = 0;
- int score = 0;
- int rounds = 0;
- int selectedColor = 0;
- // ============================================================
- // GAME COLORS
- // ============================================================
- struct GameColor { const char *name; uint16_t color; };
- GameColor gameColors[] =
- {
- { "RED", RGB565_RED },
- { "GREEN", RGB565_GREEN },
- { "BLUE", RGB565_BLUE },
- { "YELLOW", RGB565_YELLOW },
- { "PURPLE", RGB565_MAGENTA },
- { "CYAN", RGB565_CYAN }
- };
- const int GAME_COLOR_COUNT = sizeof(gameColors) / sizeof(gameColors[0]);
- // ============================================================
- // HOME SCREEN
- // ============================================================
- #define COLOR_SOFT_PINK 0xfe19
- #define COLOR_PINK_DARK 0xD828
- #define COLOR_ICON_GREEN 0x16dd // this is actually a baby blue
- #define COLOR_ICON_CYAN 0xe864 // this is actually red
- #define COLOR_ICON_BLUE 0x0CAE
- enum ScreenState { SCREEN_HOME, SCREEN_MONITORING, SCREEN_GAME, SCREEN_CALC };
- ScreenState currentScreen = SCREEN_HOME;
- const int ICON_SIZE = 100;
- const int ICON_GAP = 25;
- const int ICON_Y = 140;
- const int ICON_LABEL_Y = 250;
- int iconX[3] = { 25, 155, 285 };
- // ============================================================
- // CALCULATOR
- // ============================================================
- bool calcActive = false;
- float calcDisplay = 0.0f;
- float calcFirstOperand = 0.0f;
- char calcOperator = 0;
- bool calcNewInput = true;
- bool calcError = false;
- bool calcHasDecimal = false;
- float calcDecimalPlace = 0.1f;
- const int CALC_BTN_SIZE = 72;
- const int CALC_BTN_GAP = 6;
- const int CALC_BTN_START_Y = 120;
- const int CALC_BTN_START_X = 52;
- const int CALC_COLS = 4;
- const int CALC_ROWS = 4;
- const int CALC_ROWS_TOTAL = 5;
- const char *calcButtons[5][4] = {
- {"7", "8", "9", "/"},
- {"4", "5", "6", "*"},
- {"1", "2", "3", "-"},
- {"0", ".", "=", "+"},
- {"C", "C", "C", "C"}
- };
- // ============================================================
- // AUDIO / ES8311
- // ============================================================
- es8311_handle_t es_handle = NULL;
- bool audioReady = false;
- bool sirenActive = false;
- TaskHandle_t sirenTaskHandle = NULL;
- // ============================================================
- // FORWARD DECLARATIONS
- // ============================================================
- void updateMonitoringValues();
- void updateClockDisplay();
- void drawMonitoringScreen();
- void drawPossibleSeizureScreen();
- void drawAlertScreen();
- void drawCooldownScreen();
- void triggerAlert();
- void resetMonitor();
- void startGame();
- void newGameRound();
- void exitGame();
- void drawGameQuestion();
- void drawGameReveal();
- void processMonitoringTouch(int x, int y);
- void processGameTouch(int x, int y);
- void processTouch();
- bool readTouch(int &x, int &y);
- void checkResetButton();
- void updateSeizureDetection();
- void printSensorData();
- void printPowerDiagnostics();
- void i2cScan();
- void systemHeartbeat();
- bool initPower();
- void updateBatteryData();
- bool initAudio();
- void sirenTask(void *parameter);
- void startSiren();
- void stopSiren();
- void enablePowerADC();
- void disablePowerADC();
- // ============================================================
- // SCREEN ON / OFF HELPERS
- // ============================================================
- //
- // These functions only control logical drawing.
- // The actual AMOLED power rail control is below.
- //
- void screenOn()
- {
- if (!screenIsOn)
- screenHardwareOn();
- }
- void screenOff()
- {
- screenHardwareOff();
- }
- // ============================================================
- // HOME SCREEN ICONS
- // ============================================================
- // Shadow + card base, shared by all three home icons
- void iconCard(int x, uint16_t cardColor)
- {
- gfx->fillRoundRect(x + 3, ICON_Y + 4, ICON_SIZE, ICON_SIZE, 14, COLOR_SOFT_PINK);
- gfx->fillRoundRect(x, ICON_Y, ICON_SIZE, ICON_SIZE, 14, cardColor);
- }
- // Thick line (used for the ECG trace)
- void iconLine(int x0, int y0, int x1, int y1, int w, uint16_t color)
- {
- int steps = max(abs(x1 - x0), abs(y1 - y0));
- if (steps <= 0)
- {
- gfx->fillRect(x0, y0, w, w, color);
- return;
- }
- for (int i = 0; i <= steps; i++)
- {
- int x = x0 + ((x1 - x0) * i) / steps;
- int y = y0 + ((y1 - y0) * i) / steps;
- gfx->fillRect(x, y, w, w, color);
- }
- }
- // --- Monitor icon: medical monitor + ECG pulse ---
- void drawMonitorIcon(int x)
- {
- iconCard(x, COLOR_ICON_GREEN);
- // Screen
- gfx->fillRoundRect(x + 12, ICON_Y + 12, 76, 60, 8, 0x0838);
- // Status LED
- gfx->fillCircle(x + 21, ICON_Y + 21, 3, COLOR_YELLOW);
- // ECG trace
- iconLine(x + 18, ICON_Y + 44, x + 30, ICON_Y + 44, 3, 0xEFFF);
- iconLine(x + 30, ICON_Y + 44, x + 36, ICON_Y + 30, 3, 0xEFFF);
- iconLine(x + 36, ICON_Y + 30, x + 44, ICON_Y + 56, 3, 0xEFFF);
- iconLine(x + 44, ICON_Y + 56, x + 50, ICON_Y + 28, 3, 0xEFFF);
- iconLine(x + 50, ICON_Y + 28, x + 58, ICON_Y + 44, 3, 0xEFFF);
- iconLine(x + 58, ICON_Y + 44, x + 80, ICON_Y + 44, 3, 0xEFFF);
- // Stand
- gfx->fillRect(x + 44, ICON_Y + 72, 12, 8, 0xEFFF);
- gfx->fillRect(x + 34, ICON_Y + 78, 32, 5, 0xEFFF);
- }
- // --- Color icon: 8-segment color wheel ---
- void drawColorIcon(int x)
- {
- iconCard(x, COLOR_ICON_CYAN);
- int cx = x + 50;
- int cy = ICON_Y + 50;
- int r = 34;
- int h = 24;
- // Eight pie sectors
- gfx->fillTriangle(cx, cy, cx, cy - r, cx + h, cy - h, COLOR_RED);
- gfx->fillTriangle(cx, cy, cx + h, cy - h, cx + r, cy, COLOR_ORANGE);
- gfx->fillTriangle(cx, cy, cx + r, cy, cx + h, cy + h, COLOR_YELLOW);
- gfx->fillTriangle(cx, cy, cx + h, cy + h, cx, cy + r, COLOR_GREEN);
- gfx->fillTriangle(cx, cy, cx, cy + r, cx - h, cy + h, COLOR_CYAN);
- gfx->fillTriangle(cx, cy, cx - h, cy + h, cx - r, cy, COLOR_BLUE);
- gfx->fillTriangle(cx, cy, cx - r, cy, cx - h, cy - h, COLOR_PURPLE);
- gfx->fillTriangle(cx, cy, cx - h, cy - h, cx, cy - r, 0xFDBB);
- // Center hub
- gfx->fillCircle(cx, cy, 12, 0xEFFF);
- gfx->fillCircle(cx, cy, 5, COLOR_ICON_CYAN);
- }
- // --- Calc icon: mini calculator with screen and keys ---
- void drawCalcIcon(int x)
- {
- iconCard(x, COLOR_ICON_BLUE);
- // LCD screen
- gfx->fillRoundRect(x + 14, ICON_Y + 14, 72, 26, 6, 0xEFFF);
- gfx->setTextColor(COLOR_ICON_BLUE);
- gfx->setTextSize(1);
- gfx->setCursor(x + 56, ICON_Y + 19);
- gfx->println("123");
- // 3x3 key grid, orange = key
- for (int r = 0; r < 3; r++)
- {
- for (int c = 0; c < 3; c++)
- {
- int bx = x + 16 + c * 22;
- int by = ICON_Y + 48 + r * 16;
- uint16_t color;
- color = (r == 2 && c == 2) ? COLOR_ORANGE : 0xEFFF;
- gfx->fillRoundRect(bx, by, 16, 10, 3, color);
- }
- }
- }
- // ============================================================
- // HOME SCREEN
- // ============================================================
- void drawHomeScreen()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(COLOR_SOFT_PINK);
- gfx->setTextColor(0x410F);
- gfx->setTextSize(2);
- gfx->setCursor(42, 30);
- gfx->println("Piper Hamilton 405-412-4500");
- gfx->setTextColor(0x6510);
- gfx->setTextSize(1);
- gfx->setCursor(130, 60);
- gfx->println("Tap an app");
- // Icons
- drawMonitorIcon(iconX[0]);
- drawColorIcon(iconX[1]);
- drawCalcIcon(iconX[2]);
- // Labels
- gfx->setTextColor(0x410F);
- gfx->setTextSize(1);
- gfx->setCursor(iconX[0] + 10, ICON_LABEL_Y);
- gfx->println("Monitor");
- gfx->setCursor(iconX[1] + 5, ICON_LABEL_Y);
- gfx->println("Color");
- gfx->setCursor(iconX[2] + 25, ICON_LABEL_Y);
- gfx->println("Calc");
- // Battery
- gfx->setTextColor(0x410F);
- gfx->setTextSize(2);
- gfx->setCursor(160, 460);
- if (batteryConnected)
- {
- gfx->print(batteryPercent);
- gfx->print("%");
- }
- else
- {
- gfx->println("No Battery");
- }
- // Time
- RTC_DateTime dt = rtc.getDateTime();
- char tstr[16];
- int h = dt.getHour();
- int m = dt.getMinute();
- int dh = (h == 0) ? 12 : (h > 12 ? h - 12 : h);
- snprintf(tstr, sizeof(tstr), "%d:%02d %s", dh, m, h < 12 ? "AM" : "PM");
- gfx->setTextColor(0x410F);
- gfx->setTextSize(2);
- gfx->setCursor(150, 420);
- gfx->println(tstr);
- currentScreen = SCREEN_HOME;
- }
- // ============================================================
- // HOME SCREEN TOUCH
- // ============================================================
- void processHomeScreenTouch(int x, int y)
- {
- for (int i = 0; i < 3; i++)
- {
- bool hit =
- x >= iconX[i] && x <= iconX[i] + ICON_SIZE &&
- y >= ICON_Y && y <= ICON_Y + ICON_SIZE;
- if (hit)
- {
- Serial.print("Home icon tapped: ");
- Serial.println(i);
- switch (i)
- {
- case 0:
- currentScreen = SCREEN_MONITORING;
- drawMonitoringScreen();
- break;
- case 1:
- currentScreen = SCREEN_GAME;
- startGame();
- break;
- case 2:
- currentScreen = SCREEN_CALC;
- startCalc();
- break;
- }
- return;
- }
- }
- }
- // ============================================================
- // CALCULATOR
- // ============================================================
- void startCalc()
- {
- calcActive = true;
- calcDisplay = 0.0f;
- calcFirstOperand = 0.0f;
- calcOperator = 0;
- calcNewInput = true;
- calcHasDecimal = false;
- calcError = false;
- drawCalc();
- Serial.println(">>> CALC STARTED <<<");
- }
- void drawCalc()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(COLOR_SOFT_PINK);
- gfx->setTextColor(0x410F);
- gfx->setTextSize(2);
- gfx->setCursor(160, 20);
- gfx->println("Calc");
- gfx->fillRoundRect(20, 70, 370, 45, 8, 0x0A0A);
- gfx->setTextColor(0xEFFF);
- gfx->setTextSize(3);
- gfx->setCursor(40, 80);
- if (calcError)
- gfx->println("ERROR");
- else
- gfx->println(calcDisplay, 2);
- for (int r = 0; r < CALC_ROWS_TOTAL; r++)
- {
- for (int c = 0; c < CALC_COLS; c++)
- {
- int bx = CALC_BTN_START_X + c * (CALC_BTN_SIZE + CALC_BTN_GAP);
- int by = CALC_BTN_START_Y + r * (CALC_BTN_SIZE + CALC_BTN_GAP);
- uint16_t bgColor;
- if (c == 3) bgColor = 0x0CAE;
- else if (r == 4) bgColor = 0xD828;
- else bgColor = 0xFDBB;
- gfx->fillRoundRect(bx, by, CALC_BTN_SIZE, CALC_BTN_SIZE, 10, bgColor);
- gfx->setTextColor(0x410F);
- gfx->setTextSize(2);
- gfx->setCursor(bx + 25, by + 30);
- gfx->println(calcButtons[r][c]);
- }
- }
- gfx->setTextColor(0x6510);
- gfx->setTextSize(1);
- gfx->setCursor(110, 480);
- gfx->println("GPIO0 = Home");
- currentScreen = SCREEN_CALC;
- }
- void processCalcTouch(int x, int y)
- {
- unsigned long now = millis();
- // ------------------------------------------------------------
- // TOUCH DEBOUNCE
- // ------------------------------------------------------------
- if (now - lastTouchTime < TOUCH_DEBOUNCE)
- return;
- lastTouchTime = now;
- // ------------------------------------------------------------
- // FIND THE BUTTON THAT WAS PRESSED
- // ------------------------------------------------------------
- for (int r = 0; r < CALC_ROWS_TOTAL; r++)
- {
- for (int c = 0; c < CALC_COLS; c++)
- {
- int bx = CALC_BTN_START_X +
- c * (CALC_BTN_SIZE + CALC_BTN_GAP);
- int by = CALC_BTN_START_Y +
- r * (CALC_BTN_SIZE + CALC_BTN_GAP);
- bool hit =
- x >= bx &&
- x <= bx + CALC_BTN_SIZE &&
- y >= by &&
- y <= by + CALC_BTN_SIZE;
- if (!hit)
- continue;
- const char *btn = calcButtons[r][c];
- char key = btn[0];
- // ====================================================
- // ERROR STATE
- // ====================================================
- // Once we have an error, only C will do anything.
- if (calcError && key != 'C')
- return;
- // ====================================================
- // CLEAR
- // ====================================================
- if (key == 'C')
- {
- calcDisplay = 0.0f;
- calcFirstOperand = 0.0f;
- calcOperator = 0;
- calcNewInput = true;
- calcHasDecimal = false;
- calcDecimalPlace = 0.1f;
- calcError = false;
- drawCalc();
- return;
- }
- // ====================================================
- // NUMBER BUTTON
- // ====================================================
- if (key >= '0' && key <= '9')
- {
- int digit = key - '0';
- // ------------------------------------------------
- // We're starting a new number.
- //
- // Example:
- //
- // 7 * 8
- // ^
- // 8 starts a new number.
- // ------------------------------------------------
- if (calcNewInput)
- {
- calcDisplay = (float)digit;
- calcNewInput = false;
- calcHasDecimal = false;
- calcDecimalPlace = 0.1f;
- }
- else
- {
- // ------------------------------------------------
- // Normal whole-number entry.
- // Example: 1 -> 12 -> 123
- // ------------------------------------------------
- if (!calcHasDecimal)
- {
- calcDisplay =
- calcDisplay * 10.0f + (float)digit;
- }
- // ------------------------------------------------
- // Decimal-number entry.
- //
- // Example:
- //
- // 7 . 5
- //
- // calcDisplay becomes 7.5
- // ------------------------------------------------
- else
- {
- calcDisplay +=
- (float)digit * calcDecimalPlace;
- calcDecimalPlace *= 0.1f;
- }
- }
- drawCalc();
- return;
- }
- // ====================================================
- // DECIMAL POINT
- // ====================================================
- if (key == '.')
- {
- // Don't allow two decimal points in one number.
- if (calcHasDecimal)
- return;
- // If "." is the first thing entered after an
- // operator, start with 0.
- //
- // Example:
- //
- // 7 * . 5
- //
- // becomes 7 * 0.5
- if (calcNewInput)
- {
- calcDisplay = 0.0f;
- calcNewInput = false;
- }
- calcHasDecimal = true;
- calcDecimalPlace = 0.1f;
- drawCalc();
- return;
- }
- // ====================================================
- // ARITHMETIC OPERATOR
- // ====================================================
- if (key == '+' ||
- key == '-' ||
- key == '*' ||
- key == '/')
- {
- // ------------------------------------------------
- // If an operator was already pressed, but the user
- // hasn't entered the next number yet, simply
- // replace the operator.
- //
- // Example:
- //
- // 7 * +
- //
- // becomes:
- //
- // 7 +
- // ------------------------------------------------
- if (calcOperator != 0 && calcNewInput)
- {
- calcOperator = key;
- drawCalc();
- return;
- }
- // ------------------------------------------------
- // FIRST OPERATOR
- //
- // Example:
- //
- // 7 *
- //
- // Save 7 as the first operand.
- //
- // IMPORTANT:
- // We DO NOT calculate 7 * 7 here.
- // ------------------------------------------------
- if (calcOperator == 0)
- {
- calcFirstOperand = calcDisplay;
- }
- // ------------------------------------------------
- // SECOND OPERATOR AFTER A NUMBER
- //
- // Example:
- //
- // 7 + 3 *
- //
- // First calculate 7 + 3 = 10.
- // Then remember that "*" is the next operation.
- // ------------------------------------------------
- else
- {
- switch (calcOperator)
- {
- case '+':
- calcFirstOperand =
- calcFirstOperand + calcDisplay;
- break;
- case '-':
- calcFirstOperand =
- calcFirstOperand - calcDisplay;
- break;
- case '*':
- calcFirstOperand =
- calcFirstOperand * calcDisplay;
- break;
- case '/':
- if (calcDisplay == 0.0f)
- {
- calcError = true;
- drawCalc();
- return;
- }
- calcFirstOperand =
- calcFirstOperand / calcDisplay;
- break;
- }
- calcDisplay = calcFirstOperand;
- }
- // Save the newly pressed operator.
- calcOperator = key;
- // The next digit begins a new number.
- calcNewInput = true;
- calcHasDecimal = false;
- calcDecimalPlace = 0.1f;
- drawCalc();
- return;
- }
- // ====================================================
- // EQUALS
- // ====================================================
- if (key == '=')
- {
- // Nothing to calculate if there is no operator.
- if (calcOperator == 0)
- {
- drawCalc();
- return;
- }
- // If the user presses "=" immediately after the
- // operator, don't calculate yet.
- //
- // Example:
- //
- // 7 *
- // =
- //
- // Just leave 7 on the display.
- if (calcNewInput)
- {
- drawCalc();
- return;
- }
- // ------------------------------------------------
- // Perform the pending calculation.
- // ------------------------------------------------
- switch (calcOperator)
- {
- case '+':
- calcDisplay =
- calcFirstOperand + calcDisplay;
- break;
- case '-':
- calcDisplay =
- calcFirstOperand - calcDisplay;
- break;
- case '*':
- calcDisplay =
- calcFirstOperand * calcDisplay;
- break;
- case '/':
- if (calcDisplay == 0.0f)
- {
- calcError = true;
- drawCalc();
- return;
- }
- calcDisplay =
- calcFirstOperand / calcDisplay;
- break;
- }
- // The result becomes the new first operand.
- calcFirstOperand = calcDisplay;
- // Operation is finished.
- calcOperator = 0;
- calcNewInput = true;
- calcHasDecimal = false;
- calcDecimalPlace = 0.1f;
- drawCalc();
- return;
- }
- // If we get here, it wasn't a recognized calculator key.
- return;
- }
- }
- }
- // ============================================================
- // BATTERY HELPERS
- // ============================================================
- void enablePowerADC()
- {
- if (!powerReady) return;
- power.enableTemperatureMeasure();
- power.enableBattDetection();
- power.enableVbusVoltageMeasure();
- power.enableBattVoltageMeasure();
- power.enableSystemVoltageMeasure();
- }
- void disablePowerADC()
- {
- if (!powerReady) return;
- power.disableTemperatureMeasure();
- power.disableBattDetection();
- power.disableVbusVoltageMeasure();
- power.disableBattVoltageMeasure();
- power.disableSystemVoltageMeasure();
- }
- // ============================================================
- // I2C SCANNER
- // ============================================================
- void i2cScan()
- {
- Serial.println();
- Serial.println("========== I2C SCAN ==========");
- uint8_t devicesFound = 0;
- for (uint8_t address = 1; address < 127; address++)
- {
- Wire.beginTransmission(address);
- uint8_t error = Wire.endTransmission();
- if (error == 0)
- {
- Serial.print("I2C device found at 0x");
- if (address < 16) Serial.print("0");
- Serial.println(address, HEX);
- devicesFound++;
- }
- }
- if (devicesFound == 0)
- Serial.println("No I2C devices found.");
- else
- {
- Serial.print("I2C devices found: ");
- Serial.println(devicesFound);
- }
- Serial.println("==============================");
- Serial.println();
- }
- // ============================================================
- // AXP2101 DIAGNOSTICS
- // ============================================================
- void printPowerDiagnostics()
- {
- if (!powerReady)
- {
- Serial.println("POWER DIAGNOSTICS: PMU NOT READY");
- return;
- }
- uint16_t status = power.status();
- uint8_t status1 = (status >> 8) & 0xFF;
- uint8_t status2 = status & 0xFF;
- Serial.println();
- Serial.println("========== AXP2101 ==========");
- Serial.print("Chip ID: 0x");
- Serial.println(power.getChipID(), HEX);
- Serial.print("STATUS1: 0x");
- if (status1 < 0x10) Serial.print("0");
- Serial.println(status1, HEX);
- Serial.print("STATUS2: 0x");
- if (status2 < 0x10) Serial.print("0");
- Serial.println(status2, HEX);
- Serial.print("Battery connected: ");
- Serial.println(power.isBatteryConnect() ? "YES" : "NO");
- Serial.print("Charging: ");
- Serial.println(power.isCharging() ? "YES" : "NO");
- Serial.print("Discharging: ");
- Serial.println(power.isDischarge() ? "YES" : "NO");
- Serial.print("Standby: ");
- Serial.println(power.isStandby() ? "YES" : "NO");
- Serial.print("VBUS input: ");
- Serial.println(power.isVbusIn() ? "YES" : "NO");
- Serial.print("VBUS good: ");
- Serial.println(power.isVbusGood() ? "YES" : "NO");
- Serial.print("Battery voltage: ");
- Serial.print(power.getBattVoltage());
- Serial.println(" mV");
- Serial.print("VBUS voltage: ");
- Serial.print(power.getVbusVoltage());
- Serial.println(" mV");
- Serial.print("System voltage: ");
- Serial.print(power.getSystemVoltage());
- Serial.println(" mV");
- Serial.print("Battery percentage: ");
- if (power.isBatteryConnect())
- {
- Serial.print(power.getBatteryPercent());
- Serial.println("%");
- }
- else
- Serial.println("N/A");
- Serial.print("Charger status: ");
- Serial.println(power.getChargerStatus());
- Serial.print("Thermal regulation: ");
- Serial.println(power.getThermalRegulationStatus() ? "YES" : "NO");
- Serial.print("Current limit status: ");
- Serial.println(power.getCurrentLimitStatus() ? "YES" : "NO");
- Serial.println("=============================");
- Serial.println();
- }
- // ============================================================
- // SYSTEM HEARTBEAT
- // ============================================================
- void systemHeartbeat()
- {
- unsigned long now = millis();
- if (now - lastHeartbeat < HEARTBEAT_INTERVAL) return;
- lastHeartbeat = now;
- Serial.println();
- Serial.println("========== SYSTEM HEARTBEAT ==========");
- Serial.print("Uptime: ");
- Serial.print(now / 1000);
- Serial.println(" seconds");
- Serial.print("Sensor samples: ");
- Serial.println(sensorSampleCount);
- Serial.print("Free heap: ");
- Serial.print(ESP.getFreeHeap());
- Serial.println(" bytes");
- Serial.print("Free PSRAM: ");
- Serial.print(ESP.getFreePsram());
- Serial.println(" bytes");
- Serial.print("Battery: ");
- if (batteryConnected)
- {
- Serial.print(batteryPercent);
- Serial.print("% / ");
- Serial.print(batteryVoltage);
- Serial.println(" mV");
- }
- else
- Serial.println("NOT CONNECTED");
- Serial.print("VBUS: ");
- Serial.print(vbusVoltage);
- Serial.println(" mV");
- Serial.print("System voltage: ");
- Serial.print(systemVoltage);
- Serial.println(" mV");
- Serial.print("Screen: ");
- Serial.println(screenIsOn ? "ON" : "OFF (panel gated)");
- Serial.print("Monitor state: ");
- switch (monitorState)
- {
- case MONITORING: Serial.println("MONITORING"); break;
- case POSSIBLE_SEIZURE: Serial.println("POSSIBLE_SEIZURE"); break;
- case ALERT: Serial.println("ALERT"); break;
- case COOLDOWN: Serial.println("COOLDOWN"); break;
- }
- printPowerDiagnostics();
- Serial.println("=======================================");
- Serial.println();
- }
- // ============================================================
- // INITIALIZE AXP2101
- // ============================================================
- bool initPower()
- {
- Serial.println("Initializing AXP2101 power management...");
- Serial.print("PMU I2C SDA: ");
- Serial.println(IIC_SDA);
- Serial.print("PMU I2C SCL: ");
- Serial.println(IIC_SCL);
- Serial.print("PMU address: 0x");
- Serial.println(AXP2101_SLAVE_ADDRESS, HEX);
- if (!power.begin(Wire, AXP2101_SLAVE_ADDRESS, IIC_SDA, IIC_SCL))
- {
- Serial.println("AXP2101 initialization FAILED.");
- return false;
- }
- Serial.println("AXP2101 responded.");
- Serial.print("AXP2101 Chip ID: 0x");
- Serial.println(power.getChipID(), HEX);
- power.disableTSPinMeasure();
- power.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
- power.clearIrqStatus();
- power.enableTemperatureMeasure();
- power.enableBattDetection();
- power.enableVbusVoltageMeasure();
- power.enableBattVoltageMeasure();
- power.enableSystemVoltageMeasure();
- powerReady = true;
- Serial.println("AXP2101 power management ready.");
- printPowerDiagnostics();
- return true;
- }
- // ============================================================
- // UPDATE BATTERY DATA
- // ============================================================
- void updateBatteryData()
- {
- if (!powerReady) return;
- unsigned long now = millis();
- if (now - lastBatteryUpdate < BATTERY_UPDATE_INTERVAL) return;
- lastBatteryUpdate = now;
- batteryConnected = power.isBatteryConnect();
- batteryCharging = power.isCharging();
- batteryVoltage = power.getBattVoltage();
- vbusVoltage = power.getVbusVoltage();
- systemVoltage = power.getSystemVoltage();
- if (batteryConnected)
- {
- batteryPercent = power.getBatteryPercent();
- if (batteryPercent < 0) batteryPercent = 0;
- if (batteryPercent > 100) batteryPercent = 100;
- }
- else
- batteryPercent = 0;
- Serial.print("BATTERY: ");
- if (batteryConnected)
- {
- Serial.print(batteryPercent);
- Serial.print("% ");
- Serial.print(batteryVoltage);
- Serial.print("mV");
- }
- else
- Serial.print("NOT CONNECTED");
- Serial.print(" CHARGING=");
- Serial.print(batteryCharging ? "YES" : "NO");
- Serial.print(" VBUS=");
- Serial.print(vbusVoltage);
- Serial.print("mV");
- Serial.print(" VSYS=");
- Serial.print(systemVoltage);
- Serial.println("mV");
- }
- // ============================================================
- // DRAW BATTERY ICON
- // ============================================================
- void drawBatteryIcon()
- {
- const int bx = 335;
- const int by = 20;
- const int bw = 48;
- const int bh = 22;
- gfx->fillRect(325, 10, 85, 40, COLOR_BG);
- gfx->drawRect(bx, by, bw, bh, COLOR_WHITE);
- gfx->fillRect(bx + bw, by + 6, 4, 10, COLOR_WHITE);
- int fillWidth = ((bw - 4) * batteryPercent) / 100;
- uint16_t fillColor = COLOR_GREEN;
- if (batteryPercent <= 20) fillColor = COLOR_RED;
- else if (batteryPercent <= 40) fillColor = COLOR_YELLOW;
- if (fillWidth > 0)
- gfx->fillRect(bx + 2, by + 2, fillWidth, bh - 4, fillColor);
- if (batteryCharging)
- {
- gfx->setTextColor(COLOR_YELLOW);
- gfx->setTextSize(2);
- gfx->setCursor(290, 18);
- gfx->print("+");
- }
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(1);
- gfx->setCursor(335, 45);
- if (batteryConnected)
- {
- gfx->print(batteryPercent);
- gfx->print("%");
- }
- else
- gfx->print("--%");
- }
- // ============================================================
- // AUDIO INITIALIZATION
- // ============================================================
- bool initAudio()
- {
- Serial.println("Initializing ES8311 audio...");
- i2s.setPins(AUDIO_SCLK, AUDIO_LRCK, AUDIO_DSDIN, AUDIO_ASDOUT, AUDIO_MCLK);
- if (!i2s.begin(I2S_MODE_STD, AUDIO_SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT,
- I2S_SLOT_MODE_STEREO, I2S_STD_SLOT_BOTH))
- {
- Serial.println("I2S initialization failed.");
- return false;
- }
- Wire.begin(IIC_SDA, IIC_SCL);
- pinMode(AUDIO_PA_CTRL, OUTPUT);
- digitalWrite(AUDIO_PA_CTRL, HIGH);
- es_handle = es8311_create(AUDIO_I2C_NUM, ES8311_ADDRRES_0);
- if (!es_handle)
- {
- Serial.println("ES8311 create failed.");
- return false;
- }
- const es8311_clock_config_t es_clk = {
- .mclk_inverted = false,
- .sclk_inverted = false,
- .mclk_from_mclk_pin = true,
- .mclk_frequency = AUDIO_SAMPLE_RATE * 256,
- .sample_frequency = AUDIO_SAMPLE_RATE
- };
- if (es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16) != ESP_OK)
- {
- Serial.println("ES8311 init failed.");
- return false;
- }
- if (es8311_sample_frequency_config(es_handle, es_clk.mclk_frequency, es_clk.sample_frequency) != ESP_OK)
- {
- Serial.println("ES8311 sample rate configuration failed.");
- return false;
- }
- if (es8311_voice_volume_set(es_handle, AUDIO_VOLUME, NULL) != ESP_OK)
- {
- Serial.println("ES8311 volume configuration failed.");
- return false;
- }
- audioReady = true;
- Serial.println("ES8311 audio ready.");
- return true;
- }
- // ============================================================
- // SIREN TASK
- // ============================================================
- void sirenTask(void *parameter)
- {
- const int samplesPerTone = 1600;
- int16_t *buffer = (int16_t *)malloc(samplesPerTone * 2 * sizeof(int16_t));
- if (!buffer)
- {
- Serial.println("Siren audio buffer allocation failed.");
- sirenTaskHandle = NULL;
- vTaskDelete(NULL);
- return;
- }
- float phase = 0.0f;
- while (true)
- {
- if (!sirenActive)
- {
- vTaskDelay(pdMS_TO_TICKS(50));
- continue;
- }
- for (int i = 0; i < samplesPerTone; i++)
- {
- float frequency;
- if (((millis() / 350) % 2) == 0)
- frequency = 700.0f;
- else
- frequency = 1100.0f;
- phase += (2.0f * PI * frequency) / AUDIO_SAMPLE_RATE;
- if (phase >= 2.0f * PI)
- phase -= 2.0f * PI;
- int16_t sample = (int16_t)(sin(phase) * 11000.0f);
- buffer[i * 2] = sample;
- buffer[i * 2 + 1] = sample;
- }
- i2s.write((uint8_t *)buffer, samplesPerTone * 2 * sizeof(int16_t));
- }
- }
- // ============================================================
- // START / STOP SIREN
- // ============================================================
- void startSiren()
- {
- if (!audioReady) return;
- sirenActive = true;
- }
- void stopSiren()
- {
- sirenActive = false;
- }
- // ============================================================
- // MONITORING SCREEN
- // ============================================================
- void drawMonitoringScreen()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(COLOR_BG);
- gfx->setTextWrap(false);
- gfx->setTextColor(COLOR_GREEN);
- gfx->setTextSize(3);
- gfx->setCursor(40, 25);
- gfx->println("Monitoring");
- drawBatteryIcon();
- gfx->setTextColor(COLOR_YELLOW);
- gfx->setTextSize(3);
- gfx->setCursor(40, 90);
- gfx->print("TOTAL");
- gfx->setTextColor(COLOR_CYAN);
- gfx->setTextSize(2);
- gfx->setCursor(40, 165);
- gfx->print("X:");
- gfx->setCursor(40, 205);
- gfx->print("Y:");
- gfx->setCursor(40, 245);
- gfx->print("Z:");
- gfx->setTextColor(COLOR_GREEN);
- gfx->setTextSize(2);
- gfx->setCursor(40, 325);
- gfx->println("MONITORING");
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(3);
- gfx->setCursor(105, 365);
- gfx->println("--:-- --");
- gfx->setTextSize(1);
- gfx->setCursor(40, 420);
- gfx->print("Motion threshold: ");
- gfx->print(MOTION_DELTA_THRESHOLD, 2);
- gfx->println(" G");
- gfx->setCursor(40, 440);
- gfx->print("Detection: ");
- gfx->print(SEIZURE_CONFIRM_TIME);
- gfx->println(" ms");
- lastDisplayedHour = -1;
- lastDisplayedMinute = -1;
- updateMonitoringValues();
- updateClockDisplay();
- currentScreen = SCREEN_MONITORING;
- }
- // ============================================================
- // MONITORING VALUES
- // ============================================================
- void updateMonitoringValues()
- {
- if(!screenIsOn)
- return;
- gfx->fillRect(130, 88, 230, 40, COLOR_BG);
- gfx->setTextColor(COLOR_YELLOW);
- gfx->setTextSize(3);
- gfx->setCursor(130, 90);
- gfx->print(total_acc, 2);
- gfx->println(" G");
- gfx->fillRect(80, 160, 220, 30, COLOR_BG);
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(2);
- gfx->setCursor(80, 165);
- gfx->print(acc.x, 2);
- gfx->println(" G");
- gfx->fillRect(80, 200, 220, 30, COLOR_BG);
- gfx->setCursor(80, 205);
- gfx->print(acc.y, 2);
- gfx->println(" G");
- gfx->fillRect(80, 240, 220, 30, COLOR_BG);
- gfx->setCursor(80, 245);
- gfx->print(acc.z, 2);
- gfx->println(" G");
- drawBatteryIcon();
- }
- // ============================================================
- // CLOCK
- // ============================================================
- void updateClockDisplay()
- {
- if (!screenIsOn) return;
- RTC_DateTime datetime = rtc.getDateTime();
- int hour = datetime.getHour();
- int minute = datetime.getMinute();
- if (hour == lastDisplayedHour && minute == lastDisplayedMinute) return;
- lastDisplayedHour = hour;
- lastDisplayedMinute = minute;
- int displayHour = hour;
- if (displayHour == 0) displayHour = 12;
- else if (displayHour > 12) displayHour -= 12;
- const char *ampm = (hour < 12) ? "AM" : "PM";
- char timeString[16];
- snprintf(timeString, sizeof(timeString), "%d:%02d %s", displayHour, minute, ampm);
- gfx->fillRect(80, 355, 260, 50, COLOR_BG);
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(3);
- gfx->setCursor(105, 365);
- gfx->println(timeString);
- }
- // ============================================================
- // POSSIBLE SEIZURE SCREEN
- // ============================================================
- void drawPossibleSeizureScreen()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(COLOR_ORANGE);
- gfx->setTextColor(COLOR_BG);
- gfx->setTextSize(3);
- gfx->setCursor(35, 65);
- gfx->println("CHECKING");
- gfx->setCursor(35, 115);
- gfx->println("MOVEMENT");
- gfx->setTextSize(2);
- gfx->setCursor(45, 210);
- gfx->println("Possible seizure");
- gfx->setCursor(45, 255);
- gfx->println("pattern detected");
- gfx->setCursor(45, 320);
- gfx->print("Motion: ");
- gfx->print(motionCount);
- }
- // ============================================================
- // ALERT SCREEN
- // ============================================================
- void drawAlertScreen()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(COLOR_RED);
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(4);
- gfx->setCursor(35, 70);
- gfx->println("ALERT");
- gfx->setCursor(35, 130);
- gfx->println("Seizure Detected");
- gfx->setTextSize(2);
- gfx->setCursor(45, 225);
- gfx->println("Movement pattern");
- gfx->setCursor(45, 265);
- gfx->println("requires attention");
- gfx->setCursor(45, 335);
- gfx->println("Press GPIO0");
- gfx->setCursor(45, 370);
- gfx->println("to acknowledge");
- }
- // ============================================================
- // COOLDOWN SCREEN
- // ============================================================
- void drawCooldownScreen()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(COLOR_BG);
- gfx->setTextColor(COLOR_YELLOW);
- gfx->setTextSize(3);
- gfx->setCursor(55, 90);
- gfx->println("COOLDOWN");
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(2);
- gfx->setCursor(45, 190);
- gfx->println("Returning to");
- gfx->setCursor(45, 230);
- gfx->println("monitoring...");
- drawBatteryIcon();
- }
- // ============================================================
- // SCREEN ON / OFF — HARDWARE POWER GATE
- // ============================================================
- void screenHardwareOn()
- {
- if(screenIsOn)
- {
- screenOnTime = millis();
- return;
- }
- Serial.println(">>> AMOLED WAKE <<<");
- digitalWrite(LCD_EN_PIN, HIGH);
- delay(200);
- gfx->begin();
- FT3168->begin();
- FT3168->IIC_Write_Device_State(
- FT3168->Arduino_IIC_Touch::Device::TOUCH_POWER_MODE,
- FT3168->Arduino_IIC_Touch::Device_Mode::TOUCH_POWER_MONITOR);
- screenIsOn = true;
- screenOnTime = millis();
- drawHomeScreen();
- }
- void screenHardwareOff()
- {
- if (!screenIsOn)
- return;
- Serial.println(">>> AMOLED SLEEP <<<");
- //
- // Stop panel activity first.
- // Some Arduino_GFX versions do not implement this,
- // so this is intentionally protected.
- //
- #ifdef ARDUINO_GFX_HAS_DISPLAY_OFF
- gfx->displayOff();
- #endif
- delay(20);
- //
- // Disable panel communication and power.
- //
- digitalWrite(LCD_CS_PIN, HIGH);
- delay(5);
- digitalWrite(LCD_EN_PIN, LOW);
- screenIsOn = false;
- screenOnTime = millis();
- Serial.println(">>> AMOLED POWER OFF <<<");
- }
- void screenWake()
- {
- screenWakeRequested = true;
- screenHardwareOn();
- screenWakeRequested = false;
- }
- void screenKeepOn()
- {
- if(screenIsOn)
- screenOnTime = millis();
- }
- // ============================================================
- // START ALERT
- // ============================================================
- void triggerAlert()
- {
- gameActive = false;
- gameState = GAME_NONE;
- waitingForSecondTap = false;
- monitorState = ALERT;
- alertStart = millis();
- stopSiren();
- screenWake();
- drawAlertScreen();
- startSiren();
- screenKeepOn();
- Serial.println();
- Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
- Serial.println(" SEIZURE ALERT");
- Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
- }
- // ============================================================
- // RESET MONITOR
- // ============================================================
- void resetMonitor()
- {
- stopSiren();
- monitorState = MONITORING;
- possibleStart = 0;
- alertStart = 0;
- cooldownStart = 0;
- motionCount = 0;
- motionWindowStart = millis();
- if(screenIsOn)
- {
- drawMonitoringScreen();
- screenKeepOn();
- }
- Serial.println(">>> MONITOR RESET <<<");
- }
- // ============================================================
- // PHYSICAL BUTTON (GPIO0)
- // ============================================================
- void checkResetButton()
- {
- bool reading = digitalRead(RESET_BUTTON_PIN);
- unsigned long now = millis();
- // ------------------------------------------------------------
- // DEBOUNCE
- // ------------------------------------------------------------
- // If the physical reading changed, start a new debounce timer.
- if (reading != lastButtonReading)
- {
- lastButtonChange = now;
- lastButtonReading = reading;
- }
- // Ignore the button until the reading has been stable.
- if (now - lastButtonChange < BUTTON_DEBOUNCE)
- return;
- // ------------------------------------------------------------
- // ONLY ACT ON A REAL STATE CHANGE
- // ------------------------------------------------------------
- if (reading == buttonState)
- return;
- buttonState = reading;
- // ------------------------------------------------------------
- // BUTTON PRESSED
- // ------------------------------------------------------------
- if (buttonState == BUTTON_PRESSED)
- {
- // If the screen is currently OFF, a single GPIO0 press
- // simply wakes it. We do NOT start a double-press here.
- if (!screenIsOn)
- {
- Serial.println("GPIO0: waking screen");
- waitingForButtonSecondPress = false;
- screenWake();
- return;
- }
- // A second press within the allowed time = DOUBLE PRESS.
- if (waitingForButtonSecondPress)
- {
- if (now - firstButtonPressTime <= BUTTON_DOUBLE_PRESS_WINDOW)
- {
- waitingForButtonSecondPress = false;
- Serial.println(">>> GPIO0 DOUBLE PRESS <<<");
- // Double press toggles the display.
- if (screenIsOn)
- {
- Serial.println(">>> SCREEN OFF <<<");
- screenHardwareOff();
- }
- else
- {
- Serial.println(">>> SCREEN WAKE <<<");
- screenWake();
- }
- return;
- }
- // The previous press was too long ago.
- // Treat this as a brand-new first press.
- waitingForButtonSecondPress = false;
- }
- // --------------------------------------------------------
- // FIRST PRESS
- // --------------------------------------------------------
- waitingForButtonSecondPress = true;
- firstButtonPressTime = now;
- Serial.println("GPIO0: first press");
- }
- // ------------------------------------------------------------
- // SINGLE PRESS TIMEOUT
- // ------------------------------------------------------------
- // If nobody pressed GPIO0 a second time within the window,
- // the first press becomes a normal single press.
- if (waitingForButtonSecondPress &&
- now - firstButtonPressTime > BUTTON_DOUBLE_PRESS_WINDOW)
- {
- waitingForButtonSecondPress = false;
- Serial.println(">>> GPIO0 SINGLE PRESS <<<");
- // If the screen somehow became OFF while waiting,
- // just wake it.
- if (!screenIsOn)
- {
- Serial.println("Screen asleep - waking only.");
- screenWake();
- return;
- }
- // --------------------------------------------------------
- // HOME / RESET
- // --------------------------------------------------------
- Serial.println(">>> HOME / RESET <<<");
- stopSiren();
- monitorState = MONITORING;
- possibleStart = 0;
- alertStart = 0;
- cooldownStart = 0;
- motionCount = 0;
- motionWindowStart = millis();
- gameActive = false;
- gameState = GAME_NONE;
- waitingForSecondTap = false;
- calcActive = false;
- currentScreen = SCREEN_HOME;
- drawHomeScreen();
- screenKeepOn();
- }
- }
- // ============================================================
- // SEIZURE DETECTION
- // ============================================================
- void updateSeizureDetection()
- {
- unsigned long now = millis();
- motion_delta = fabs(total_acc - previous_acc);
- previous_acc = total_acc;
- if (now - motionWindowStart >= SEIZURE_WINDOW)
- {
- motionWindowStart = now;
- motionCount = 0;
- }
- if (motion_delta >= MOTION_DELTA_THRESHOLD)
- motionCount++;
- if (monitorState == MONITORING)
- {
- if (motionCount >= MOTION_COUNT_THRESHOLD)
- {
- monitorState = POSSIBLE_SEIZURE;
- possibleStart = now;
- screenWake();
- drawPossibleSeizureScreen();
- screenKeepOn();
- Serial.println(">>> POSSIBLE SEIZURE PATTERN <<<");
- return;
- }
- }
- else if (monitorState == POSSIBLE_SEIZURE)
- {
- if (motionCount >= MOTION_COUNT_THRESHOLD)
- {
- if (now - possibleStart >= SEIZURE_CONFIRM_TIME)
- {
- triggerAlert();
- return;
- }
- }
- else
- {
- if (now - possibleStart > SEIZURE_CONFIRM_TIME)
- {
- monitorState = MONITORING;
- if(screenIsOn)
- {
- drawMonitoringScreen();
- screenKeepOn();
- }
- }
- }
- }
- else if (monitorState == ALERT)
- {
- if (now - alertStart >= ALERT_DURATION)
- {
- stopSiren();
- monitorState = COOLDOWN;
- cooldownStart = now;
- drawCooldownScreen();
- screenKeepOn();
- }
- }
- else if (monitorState == COOLDOWN)
- {
- if (now - cooldownStart >= ALERT_COOLDOWN)
- {
- monitorState = MONITORING;
- if(screenIsOn)
- {
- drawMonitoringScreen();
- screenKeepOn();
- }
- }
- }
- }
- // ============================================================
- // SERIAL SENSOR DATA
- // ============================================================
- void printSensorData()
- {
- unsigned long now = millis();
- if (now - lastSerial < 250) return;
- lastSerial = now;
- Serial.print("X=");
- Serial.print(acc.x, 3);
- Serial.print(" G Y=");
- Serial.print(acc.y, 3);
- Serial.print(" G Z=");
- Serial.print(acc.z, 3);
- Serial.print(" G TOTAL=");
- Serial.print(total_acc, 3);
- Serial.print(" G DELTA=");
- Serial.print(motion_delta, 3);
- Serial.print(" MOTION=");
- Serial.print(motionCount);
- Serial.print(" STATE=");
- switch (monitorState)
- {
- case MONITORING: Serial.println("MONITORING"); break;
- case POSSIBLE_SEIZURE: Serial.println("POSSIBLE_SEIZURE"); break;
- case ALERT: Serial.println("ALERT"); break;
- case COOLDOWN: Serial.println("COOLDOWN"); break;
- }
- }
- // ============================================================
- // TOUCH READING
- // ============================================================
- bool readTouch(int &x, int &y)
- {
- x = FT3168->IIC_Read_Device_Value(
- FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X);
- y = FT3168->IIC_Read_Device_Value(
- FT3168->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y);
- if (x < 0 || y < 0)
- return false;
- if (x >= LCD_WIDTH || y >= LCD_HEIGHT)
- return false;
- return true;
- }
- // ============================================================
- // MONITORING TOUCH (DOUBLE-TAP TO START GAME)
- // ============================================================
- void processMonitoringTouch(int x, int y)
- {
- unsigned long now = millis();
- if (now - lastTouchTime < TOUCH_DEBOUNCE) return;
- lastTouchTime = now;
- if (waitingForSecondTap)
- {
- if (now - firstTapTime <= DOUBLE_TAP_WINDOW)
- {
- waitingForSecondTap = false;
- Serial.println(">>> DOUBLE TAP DETECTED <<<");
- Serial.print("Second tap X=");
- Serial.print(x);
- Serial.print(" Y=");
- Serial.println(y);
- startGame();
- return;
- }
- waitingForSecondTap = false;
- Serial.println("Second tap timeout - starting new first tap.");
- }
- waitingForSecondTap = true;
- firstTapTime = now;
- Serial.println("First tap detected.");
- Serial.println("Waiting for second tap...");
- }
- // ============================================================
- // GAME SCREEN
- // ============================================================
- void drawGameQuestion()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(COLOR_BG);
- gfx->setTextColor(COLOR_CYAN);
- gfx->setTextSize(3);
- gfx->setCursor(55, 70);
- gfx->println("GUESS THE");
- gfx->setCursor(105, 120);
- gfx->println("COLOR?");
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(2);
- gfx->setCursor(90, 230);
- gfx->println("TAP TO REVEAL");
- gfx->setTextSize(2);
- gfx->setCursor(125, 430);
- gfx->print("SCORE: ");
- gfx->print(score);
- }
- // ============================================================
- // GAME REVEAL SCREEN
- // ============================================================
- void drawGameReveal()
- {
- if(!screenIsOn)
- return;
- gfx->fillScreen(gameColors[selectedColor].color);
- gfx->setTextColor(COLOR_WHITE);
- if (selectedColor == 1 || selectedColor == 3 || selectedColor == 5)
- gfx->setTextColor(COLOR_BG);
- gfx->setTextSize(4);
- gfx->setCursor(70, 120);
- gfx->println(gameColors[selectedColor].name);
- gfx->setTextSize(2);
- gfx->setCursor(65, 230);
- gfx->println("WERE YOU RIGHT?");
- gfx->fillRoundRect(25, 300, 110, 65, 10, COLOR_GREEN);
- gfx->setTextColor(COLOR_BG);
- gfx->setTextSize(2);
- gfx->setCursor(55, 323);
- gfx->println("YES");
- gfx->fillRoundRect(150, 300, 110, 65, 10, COLOR_RED);
- gfx->setTextColor(COLOR_WHITE);
- gfx->setCursor(185, 323);
- gfx->println("NO");
- gfx->fillRoundRect(275, 300, 110, 65, 10, COLOR_CYAN);
- gfx->setTextColor(COLOR_BG);
- gfx->setCursor(300, 323);
- gfx->println("EXIT");
- gfx->setTextColor(COLOR_WHITE);
- gfx->setTextSize(2);
- gfx->setCursor(130, 410);
- gfx->print("SCORE: ");
- gfx->print(score);
- }
- // ============================================================
- // START / NEW ROUND / EXIT GAME
- // ============================================================
- void startGame()
- {
- gameActive = true;
- gameState = GAME_QUESTION;
- waitingForSecondTap = false;
- score = 0;
- rounds = 0;
- Serial.println(">>> COLOR GAME STARTED <<<");
- drawGameQuestion();
- screenKeepOn();
- }
- void newGameRound()
- {
- selectedColor = random(0, GAME_COLOR_COUNT);
- gameState = GAME_REVEAL;
- drawGameReveal();
- screenKeepOn();
- }
- void exitGame()
- {
- gameActive = false;
- gameState = GAME_NONE;
- waitingForSecondTap = false;
- Serial.print("Color game finished. Score: ");
- Serial.print(score);
- Serial.print("/");
- Serial.println(rounds);
- if(screenIsOn)
- {
- drawMonitoringScreen();
- screenKeepOn();
- }
- }
- // ============================================================
- // GAME TOUCH
- // ============================================================
- void processGameTouch(int x, int y)
- {
- unsigned long now = millis();
- if (now - lastTouchTime < TOUCH_DEBOUNCE) return;
- lastTouchTime = now;
- if (gameState == GAME_QUESTION)
- {
- Serial.println("Game: question tapped");
- newGameRound();
- return;
- }
- if (gameState == GAME_REVEAL)
- {
- if (x >= 25 && x <= 135 && y >= 300 && y <= 365)
- {
- score++;
- rounds++;
- Serial.println("Game: YES");
- drawGameQuestion();
- gameState = GAME_QUESTION;
- screenKeepOn();
- return;
- }
- if (x >= 150 && x <= 260 && y >= 300 && y <= 365)
- {
- rounds++;
- Serial.println("Game: NO");
- drawGameQuestion();
- gameState = GAME_QUESTION;
- screenKeepOn();
- return;
- }
- if (x >= 275 && x <= 385 && y >= 300 && y <= 365)
- {
- Serial.println("Game: EXIT");
- exitGame();
- return;
- }
- }
- }
- // ============================================================
- // TOUCH SYSTEM
- // ============================================================
- void processTouch()
- {
- // ------------------------------------------------------------
- // NO NEW TOUCH = NOTHING TO DO
- // ------------------------------------------------------------
- //
- // The FT3168 interrupt sets this flag when a touch occurs.
- // This prevents us from continuously reading old/stale
- // touchscreen coordinates.
- //
- if (!FT3168->IIC_Interrupt_Flag)
- return;
- // Clear the event flag now that we are handling it.
- FT3168->IIC_Interrupt_Flag = false;
- int x, y;
- // ------------------------------------------------------------
- // READ THE TOUCH COORDINATES
- // ------------------------------------------------------------
- if (!readTouch(x, y))
- return;
- Serial.print("Touch X=");
- Serial.print(x);
- Serial.print(" Y=");
- Serial.println(y);
- // ------------------------------------------------------------
- // SCREEN IS OFF
- // ------------------------------------------------------------
- //
- // A real touchscreen interrupt while asleep wakes the watch.
- // IMPORTANT: We do NOT send that same touch into an app.
- //
- if (!screenIsOn)
- {
- Serial.println(">>> TOUCH WAKE <<<");
- screenWake();
- return;
- }
- // ------------------------------------------------------------
- // ALERT MODE
- // ------------------------------------------------------------
- //
- // Don't let touchscreen taps interfere with an alert.
- //
- if (monitorState == ALERT)
- return;
- // ------------------------------------------------------------
- // HOME SCREEN
- // ------------------------------------------------------------
- if (currentScreen == SCREEN_HOME)
- {
- processHomeScreenTouch(x, y);
- return;
- }
- // ------------------------------------------------------------
- // MONITORING SCREEN
- // ------------------------------------------------------------
- if (currentScreen == SCREEN_MONITORING)
- {
- if (monitorState == MONITORING)
- {
- processMonitoringTouch(x, y);
- }
- return;
- }
- // ------------------------------------------------------------
- // COLOR GAME
- // ------------------------------------------------------------
- if (currentScreen == SCREEN_GAME)
- {
- processGameTouch(x, y);
- return;
- }
- // ------------------------------------------------------------
- // CALCULATOR
- // ------------------------------------------------------------
- if (currentScreen == SCREEN_CALC)
- {
- processCalcTouch(x, y);
- return;
- }
- }
- // ============================================================
- // SETUP
- // ============================================================
- void setup()
- {
- Serial.begin(115200);
- delay(3000);
- Serial.println();
- Serial.println("========================================");
- Serial.println(" PIPER FALL / SEZURE WATCH");
- Serial.println(" ESP32-S3 + CO5300 + QMI8658 + RTC");
- Serial.println(" V2 + AXP2101 BATTERY");
- Serial.println("========================================");
- // Button
- pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);
- // Waveshare hardware
- #ifdef DEV_DEVICE_INIT
- DEV_DEVICE_INIT();
- Serial.println("Waveshare hardware initialized.");
- #else
- Serial.println("WARNING: DEV_DEVICE_INIT not defined!");
- #endif
- // LCD power rail
- pinMode(LCD_EN_PIN, OUTPUT);
- digitalWrite(LCD_EN_PIN, HIGH);
- screenIsOn = true;
- screenOnTime = millis();
- screenWakeRequested = false;
- Serial.println("LCD power rail (DSI_PWR_EN) on GPIO");
- Serial.println(LCD_EN_PIN);
- // Display
- Serial.println("Initializing AMOLED...");
- if (!gfx->begin())
- {
- Serial.println("AMOLED initialization failed!");
- while (true) delay(1000);
- }
- gfx->fillScreen(COLOR_BG);
- Serial.println("AMOLED initialized.");
- // I2C
- Wire.begin(IIC_SDA, IIC_SCL);
- Serial.println("I2C bus initialized.");
- // I2C scan
- i2cScan();
- // Power / AXP2101
- if (!initPower())
- {
- Serial.println("WARNING: AXP2101 unavailable.");
- Serial.println("Continuing without battery diagnostics.");
- }
- // Touch
- Serial.println("Initializing FT3168 touch...");
- unsigned long ft3168Start = millis();
- while (true)
- {
- if (FT3168->begin())
- {
- break;
- }
- Serial.println("FT3168 init failed, retrying...");
- delay(1000);
- if (millis() - ft3168Start > 15000)
- {
- Serial.println("FT3168 timeout - continuing without touch.");
- break;
- }
- }
- FT3168->IIC_Write_Device_State(
- FT3168->Arduino_IIC_Touch::Device::TOUCH_POWER_MODE,
- FT3168->Arduino_IIC_Touch::Device_Mode::TOUCH_POWER_MONITOR);
- Serial.println("FT3168 touch ready.");
- Serial.print("TP_INT pin: ");
- Serial.println(TP_INT);
- pinMode(TP_INT, INPUT_PULLUP);
- Serial.print("TP_INT state: ");
- Serial.println(digitalRead(TP_INT));
- // RTC
- Serial.println("Initializing PCF85063 RTC...");
- if (!rtc.begin(Wire, IIC_SDA, IIC_SCL))
- {
- Serial.println("RTC initialization failed!");
- gfx->fillScreen(COLOR_BG);
- gfx->setTextColor(COLOR_RED);
- gfx->setTextSize(3);
- gfx->setCursor(35, 100);
- gfx->println("RTC ERROR");
- while (true) delay(1000);
- }
- Serial.println("PCF85063 detected.");
- // Set RTC from compile time
- uint8_t buildMonth = 1;
- uint8_t buildDay;
- uint16_t buildYear;
- uint8_t buildHour;
- uint8_t buildMinute;
- uint8_t buildSecond;
- char monthString[4];
- int day, year;
- sscanf(__DATE__, "%3s %d %d", monthString, &day, &year);
- if (strcmp(monthString, "Jan") == 0) buildMonth = 1;
- else if (strcmp(monthString, "Feb") == 0) buildMonth = 2;
- else if (strcmp(monthString, "Mar") == 0) buildMonth = 3;
- else if (strcmp(monthString, "Apr") == 0) buildMonth = 4;
- else if (strcmp(monthString, "May") == 0) buildMonth = 5;
- else if (strcmp(monthString, "Jun") == 0) buildMonth = 6;
- else if (strcmp(monthString, "Jul") == 0) buildMonth = 7;
- else if (strcmp(monthString, "Aug") == 0) buildMonth = 8;
- else if (strcmp(monthString, "Sep") == 0) buildMonth = 9;
- else if (strcmp(monthString, "Oct") == 0) buildMonth = 10;
- else if (strcmp(monthString, "Nov") == 0) buildMonth = 11;
- else buildMonth = 12;
- buildYear = year;
- buildDay = day;
- sscanf(__TIME__, "%hhu:%hhu:%hhu", &buildHour, &buildMinute, &buildSecond);
- buildMinute += 3;
- if (buildMinute >= 60)
- {
- buildMinute -= 60;
- buildHour++;
- if (buildHour >= 24) { buildHour = 0; buildDay++; }
- }
- rtc.setDateTime(buildYear, buildMonth, buildDay, buildHour, buildMinute, buildSecond);
- RTC_DateTime rtcNow = rtc.getDateTime();
- Serial.print("RTC: ");
- Serial.print(rtcNow.getHour());
- Serial.print(":");
- Serial.print(rtcNow.getMinute());
- Serial.println();
- // QMI8658
- Serial.println("Initializing QMI8658...");
- if (!qmi.begin(Wire, QMI8658_L_SLAVE_ADDRESS, IIC_SDA, IIC_SCL))
- {
- Serial.println("QMI8658 initialization failed.");
- gfx->fillScreen(COLOR_BG);
- gfx->setTextColor(COLOR_RED);
- gfx->setTextSize(3);
- gfx->setCursor(40, 100);
- gfx->println("QMI8658");
- gfx->setCursor(40, 150);
- gfx->println("ERROR");
- while (true) delay(1000);
- }
- Serial.println("QMI8658 detected.");
- if (!qmi.configAccelerometer(
- SensorQMI8658::ACC_RANGE_4G,
- SensorQMI8658::ACC_ODR_1000Hz,
- SensorQMI8658::LPF_MODE_0))
- {
- Serial.println("Accelerometer configuration failed.");
- }
- else
- {
- Serial.println("Accelerometer configured.");
- }
- if (!qmi.enableAccelerometer())
- {
- Serial.println("Accelerometer enable failed.");
- }
- else
- {
- Serial.println("Accelerometer enabled.");
- }
- // Audio
- initAudio();
- // Siren task
- if (audioReady)
- {
- xTaskCreatePinnedToCore(
- sirenTask, "SirenTask", 4096, NULL, 1, &sirenTaskHandle, 0);
- }
- // Random
- randomSeed(micros());
- // Initial values
- motionWindowStart = millis();
- previous_acc = 1.0f;
- sensorSampleCount = 0;
- // Initial battery
- if (powerReady)
- {
- lastBatteryUpdate = millis() - BATTERY_UPDATE_INTERVAL;
- updateBatteryData();
- }
- // Initial screen
- drawHomeScreen();
- // Heartbeat
- lastHeartbeat = millis();
- Serial.println();
- Serial.println("========================================");
- Serial.println(" SYSTEM READY");
- Serial.println("========================================");
- Serial.println("GPIO0 = Single press Home / Reset");
- Serial.println("GPIO0 = Double press Screen ON/OFF");
- Serial.print("GPIO0 double-press window = ");
- Serial.print(BUTTON_DOUBLE_PRESS_WINDOW);
- Serial.println(" ms");
- Serial.println("Tap icons to launch apps");
- Serial.println("Touch controller = FT3168");
- Serial.println("Power management = AXP2101");
- Serial.print("Motion delta threshold = ");
- Serial.print(MOTION_DELTA_THRESHOLD, 3);
- Serial.println(" G");
- Serial.print("Double-tap window = ");
- Serial.print(DOUBLE_TAP_WINDOW);
- Serial.println(" ms");
- Serial.print("Touch debounce = ");
- Serial.print(TOUCH_DEBOUNCE);
- Serial.println(" ms");
- Serial.print("Screen auto-off = ");
- Serial.print(HOME_SCREEN_TIMEOUT);
- Serial.println(" ms");
- Serial.println("10-second system heartbeat enabled.");
- Serial.println();
- }
- // ============================================================
- // SCREEN AUTO OFF
- // ============================================================
- void screenAutoOffCheck()
- {
- if(!screenIsOn)
- return;
- unsigned long timeout;
- switch(currentScreen)
- {
- case SCREEN_HOME:
- timeout = HOME_SCREEN_TIMEOUT;
- break;
- case SCREEN_MONITORING:
- timeout = IM_SCREEN_TIMEOUT;
- break;
- case SCREEN_GAME:
- timeout = GAME_SCREEN_TIMEOUT;
- break;
- case SCREEN_CALC:
- timeout = CALC_SCREEN_TIMEOUT;
- break;
- default:
- timeout = HOME_SCREEN_TIMEOUT;
- break;
- }
- if(millis() - screenOnTime >= timeout)
- {
- Serial.println(">>> SCREEN TIMEOUT <<<");
- screenHardwareOff();
- }
- }
- // ============================================================
- // LOOP
- // ============================================================
- void loop()
- {
- // Physical button
- checkResetButton();
- // Touch
- processTouch();
- // Clock
- unsigned long now = millis();
- if (now - lastClockDisplay >= 1000)
- {
- lastClockDisplay = now;
- // Only redraw the clock when the panel is actually on.
- if (screenIsOn && monitorState == MONITORING && currentScreen == SCREEN_MONITORING)
- updateClockDisplay();
- }
- // Screen auto-off
- screenAutoOffCheck();
- // Battery
- updateBatteryData();
- // Accelerometer
- if (qmi.getDataReady())
- {
- if (qmi.getAccelerometer(acc.x, acc.y, acc.z))
- {
- sensorSampleCount++;
- total_acc = sqrt((acc.x * acc.x) + (acc.y * acc.y) + (acc.z * acc.z));
- printSensorData();
- updateSeizureDetection();
- now = millis();
- // Only redraw monitoring when the panel is actually on.
- if (screenIsOn && monitorState == MONITORING && currentScreen == SCREEN_MONITORING)
- {
- if (now - lastDisplay >= 100)
- {
- lastDisplay = now;
- updateMonitoringValues();
- }
- }
- }
- }
- // Heartbeat
- systemHeartbeat();
- // Yield
- yield();
- }
RAW Paste Data
Copied
Advertisement
