RX24-Mini-Project-Code / Animatronic_Control_System_Star_Tours_Demo.ino
Animatronic_Control_System_Star_Tours_Demo.ino
Raw
// ================================================================================
// ANIMATRONIC CONTROL SYSTEM - STAR TOURS DEMO v01
// Start a synchronized playback of the Star Tours ride performance on your Captain Rex Mini.
// Controls: 7 motors via Pololu Mini Maestro + 2 LED strings + synchronization
// ================================================================================
// Release Version: 2025.1.0
// Initial code generated by Claude Sonnet 4 on 2025-08-28
// ================================================================================

#include <FastLED.h>      // Library for controlling WS2812B LED strips
#include <HardwareSerial.h>  // Library for UART communication with Maestro

// ================================================================================
// USER CONFIGURABLE VARIABLES - EASY TO MODIFY
// ================================================================================

// LED Configuration - LED String 1 (Eyes and Mouth)
#define LED1_PIN 21           // GPIO pin for first LED string (eyes + mouth)
#define LED1_COUNT 3          // Total LEDs in first string
// #define LED1_BRIGHTNESS 70    // Overall brightness for LED string 1 (0-255) FIX - REMOVED, change brightness in EYE_BRIGHTNESS and MOUTH_BASELINE

// LED Configuration - LED String 2 (Dashboard)
#define LED2_PIN 4            // GPIO pin for second LED string (dashboard)
#define LED2_COUNT 8          // Total LEDs in second string
#define LED2_BRIGHTNESS 1    // Overall brightness for LED string 2 (0-255) (IRL:5 Camera:)

// LED Array Positions for String 1
#define EYE1_LED 0           // Left eye LED position
#define EYE2_LED 1           // Right eye LED position  
#define MOUTH_LED 2          // Mouth LED position

// LED Array Positions for String 2 (Dashboard)
#define DASH_LED1 0          // Dashboard button 1
#define DASH_LED2 1          // Dashboard button 2
#define DASH_LED3 2          // Dashboard button 3
#define DASH_LED4 3          // Dashboard button 4 (kept off, but in array)
#define DASH_LED5 4          // Dashboard button 5
#define DASH_LED6 5          // Dashboard button 6
#define DASH_LED7 6          // Dashboard button 7
#define DASH_LED8 7          // Dashboard button 8

// Eye LED Settings
#define EYE_BRIGHTNESS 10     // Eye brightness when open (0-255) (IRL:10 Camera:2)
#define BLINK_INTERVAL 3500   // Time between blinks (milliseconds)
#define BLINK_DURATION 150    // How long eyes stay closed (milliseconds)
// Eye color (Light white-blue) - RGB values
#define EYE_RED 171           // Red component of eye color
#define EYE_GREEN 207         // Green component of eye color  
#define EYE_BLUE 253          // Blue component of eye color

// Mouth LED Settings
#define MOUTH_BASELINE 10     // Baseline brightness when not speaking (0-255) (IRL:30 Camera:10)
#define MOUTH_MAX 255         // Maximum brightness during speech (0-255)
#define FADE_SPEED 12         // How fast mouth fades between levels (higher = faster)
// Mouth color (Orange) - RGB values
#define MOUTH_RED 140         // Red component of mouth color
#define MOUTH_GREEN 50        // Green component of mouth color
#define MOUTH_BLUE 0          // Blue component of mouth color

// Dashboard LED Colors - RGB values for each color state
// Warm Yellow color
#define YELLOW_RED 255        // Red component of warm yellow
#define YELLOW_GREEN 180      // Green component of warm yellow
#define YELLOW_BLUE 0         // Blue component of warm yellow
// Cool Blue color
#define BLUE_RED 0            // Red component of cool blue
#define BLUE_GREEN 100        // Green component of cool blue
#define BLUE_BLUE 255         // Blue component of cool blue
// Red color (for warning/alarm states)
#define RED_RED 255           // Red component of red
#define RED_GREEN 0           // Green component of red
#define RED_BLUE 0            // Blue component of red

// Dashboard LED Timing Settings
#define DASH_IDLE_MIN_TIME 1000    // Minimum time between blinks in idle (ms)
#define DASH_IDLE_MAX_TIME 1500    // Maximum time between blinks in idle (ms)
#define DASH_WARNING_INTERVAL 1000 // How often all LEDs flash red in warning (ms)
#define DASH_ALARM_INTERVAL 200    // How often all LEDs flash red in alarm (ms)
#define DASH_MIN_ON_LEDS 5         // Minimum LEDs that must stay on at all times

// Maestro Communication Settings
#define MAESTRO_RX_PIN 16     // ESP32 TX pin connected to Maestro RX (via level shifter)
#define MAESTRO_TX_PIN 17     // ESP32 RX pin connected to Maestro TX (via level shifter)
#define MAESTRO_BAUD 9600     // Communication speed with Maestro
#define MAESTRO_DEVICE_NUMBER 12  // Maestro device number (usually 12 for Mini Maestro)

// Timing and Synchronization Settings
#define MAX_COUNTDOWN_SECONDS 30  // Maximum countdown time allowed
#define COUNTDOWN_RESOLUTION 10   // Countdown display resolution (10ms = 0.01 seconds)

// ================================================================================
// EMBEDDED TIMING DATA ARRAYS
// ================================================================================

// Mouth LED timing data (timestamp in ms, brightness level)
const int mouthTimingData[][2] = {
  // {0,50}, {2026,255}, {2215,255}, {2226,50}, {2415,50}, {4630,255}, {4830,50}, {4878,255}, {5071,255}, {5078,50},        //original data, saved for posterity
  // {5262,255}, {5271,50}, {5390,255}, {5462,50}, {5518,255}, {5590,50}, {5654,255}, {5718,50}, {5742,255}, {5854,50}, 
  // {5886,255}, {5942,50}, {6086,50}, {6142,255}, {6342,50}, {6342,255}, {6542,50}, {6542,255}, {6734,255}, {6742,50}, 
  // {6934,50}, {7158,255}, {7358,50}, {7366,255}, {7510,255}, {7566,50}, {7654,255}, {7710,50}, {7798,255}, {7854,50}, 
  // {7942,255}, {7998,50}, {8102,255}, {8142,50}, {8302,50}, {8454,255}, {8654,50}, {8758,255}, {8934,255}, {8958,50}, 
  // {9134,50}, {9358,255}, {9510,255}, {9558,50}, {9710,50}, {9846,255}, {10030,255}, {10046,50}, {10182,255}, {10230,50}, 
  // {10326,255}, {10382,50}, {10526,50}, {10566,255}, {10766,50}, {16046,255}, {16246,50}, {16350,255}, {16550,50}, {17070,255}, 
  // {17270,50}, {17334,255}, {17534,50}, {17654,255}, {17854,50}, {17926,255}, {18126,50}, {26389,255}, {26589,50}, {26901,255}, 
  // {27078,255}, {27101,50}, {27278,50}, {27333,255}, {27533,50}, {27565,255}, {27765,50}, {27957,255}, {28157,50}, {35341,255}, 
  // {35517,255}, {35541,50}, {35661,255}, {35717,50}, {35861,50}, {36069,255}, {36237,255}, {36269,50}, {36405,255}, {36437,50}, 
  // {36605,50}, {37053,255}, {37253,50}, {37837,255}, {38037,50}, {38693,255}, {38821,255}, {38893,50}, {38965,255}, {39021,50}, 
  // {39102,255}, {39165,50}, {39221,255}, {39302,50}, {39421,50}, {40101,255}, {40253,255}, {40301,50}, {40381,255}, {40453,50}, 
  // {40493,255}, {40581,50}, {40605,255}, {40693,50}, {40709,255}, {40805,50}, {40829,255}, {40909,50}, {40941,255}, {41029,50}, 
  // {41061,255}, {41141,50}, {41181,255}, {41261,50}, {41293,255}, {41381,50}, {41413,255}, {41493,50}, {41533,255}, {41613,50}, 
  // {41669,255}, {41733,50}, {41869,50}, {52077,255}, {52277,50}, {52429,255}, {52581,255}, {52629,50}, {52733,255}, {52781,50}, 
  // {52861,255}, {52933,50}, {53061,50}, {53485,255}, {53629,255}, {53685,50}, {53773,255}, {53829,50}, {53933,255}, {53973,50}, 
  // {54133,50}, {54549,255}, {54693,255}, {54749,50}, {54893,50}, {56493,255}, {56685,255}, {56693,50}, {56885,50}, {57693,255}, 
  // {57893,50}, {58021,255}, {58221,50}, {58405,255}, {58605,50}, {58605,255}, {58805,50}, {58957,255}, {59157,50}, {67692,255}, 
  // {67876,255}, {67892,50}, {68068,255}, {68076,50}, {68268,50}, {68420,255}, {68620,50}, {68644,255}, {68844,50}, {68892,255}, 
  // {69092,50}, {69124,255}, {69324,50}, {69356,255}, {69556,50}, {71300,255}, {71460,255}, {71500,50}, {71596,255}, {71660,50}, 
  // {71788,255}, {71796,50}, {71924,255}, {71988,50}, {72124,50}, {72724,255}, {72876,255}, {72924,50}, {73076,50}, {73692,255}, 
  // {73892,50}, {73940,255}, {74140,50}, {75813,255}, {75972,255}, {76013,50}, {76108,255}, {76172,50}, {76244,255}, {76308,50}, 
  // {76380,255}, {76444,50}, {76580,50}, {76612,255}, {76772,255}, {76812,50}, {76932,255}, {76972,50}, {77124,255}, {77132,50}, 
  // {77324,50}, {77380,255}, {77532,255}, {77580,50}, {77700,255}, {77732,50}, {77876,255}, {77900,50}, {78052,255}, {78076,50}, 
  // {78252,50}, {78276,255}, {78444,255}, {78476,50}, {78620,255}, {78644,50}, {78820,50}, {78844,255}, {79044,50}, {79060,255}, 
  // {79260,50}, {92667,255}, {92843,255}, {92867,50}, {92963,255}, {93043,50}, {93132,255}, {93163,50}, {93267,255}, {93332,50}, 
  // {93411,255}, {93467,50}, {93547,255}, {93611,50}, {93667,255}, {93747,50}, {93804,255}, {93867,50}, {93931,255}, {94004,50}, 
  // {94043,255}, {94131,50}, {94180,255}, {94243,50}, {94324,255}, {94380,50}, {94444,255}, {94524,50}, {94563,255}, {94644,50}, 
  // {94763,50}

  //From timing_file_02_ARRAY_READY.tim
  {0,50}, {2650,255}, {2850,50}, {2986,255}, {3186,50}, {5617,255}, {5802,255}, {5817,50}, {5985,255}, {6002,50}, {6162,255}, {6185,50}, {6273,255}, {6362,50}, {6362,255}, {6473,50}, {6505,255}, {6562,50}, {6601,255}, {6705,50}, {6705,255}, {6801,50}, {6810,255}, {6905,50}, {6985,255}, {7010,50}, {7097,255}, {7185,50}, {7265,255}, {7297,50}, {7410,255}, {7465,50}, {7577,255}, {7610,50}, {7777,50}, {8025,255}, {8225,50}, {8226,255}, {8353,255}, {8426,50}, {8505,255}, {8553,50}, {8642,255}, {8705,50}, {8737,255}, {8842,50}, {8897,255}, {8937,50}, {9097,50}, {9361,255}, {9561,50}, {9601,255}, {9801,50}, {9802,255}, {10002,50}, {10161,255}, {10297,255}, {10361,50}, {10497,50}, {10609,255}, {10809,50}, {10809,255}, {11009,50}, {11025,255}, {11137,255}, {11225,50}, {11321,255}, {11337,50}, {11401,255}, {11521,50}, {11601,50}, {16929,255}, {17049,255}, {17129,50}, {17217,255}, {17249,50}, {17353,255}, {17417,50}, {17473,255}, {17553,50}, {17673,50}, {17953,255}, {18153,50}, {18225,255}, {18361,255}, {18425,50}, {18561,50}, {18633,255}, {18833,50}, {27065,255}, {27265,50}, {27585,255}, {27785,50}, {27801,255}, {28001,50}, {28033,255}, {28153,255}, {28233,50}, {28353,50}, {28393,255}, {28593,50}, {28665,255}, {28865,50}, {35665,255}, {35857,255}, {35865,50}, {36057,50}, {36400,255}, {36600,50}, {36785,255}, {36985,50}, {37585,255}, {37785,50}, {38288,255}, {38488,50}, {39209,255}, {39304,255}, {39408,255}, {39409,50}, {39497,255}, {39504,50}, {39608,50}, {39609,255}, {39697,50}, {39704,255}, {39809,50}, {39904,50}, {40489,255}, {40560,255}, {40656,255}, {40689,50}, {40720,255}, {40760,50}, {40793,255}, {40856,50}, {40856,255}, {40913,255}, {40920,50}, {40984,255}, {40993,50}, {41032,255}, {41056,50}, {41112,255}, {41113,50}, {41152,255}, {41184,50}, {41224,255}, {41232,50}, {41264,255}, {41312,50}, {41345,255}, {41352,50}, {41368,255}, {41424,50}, {41456,255}, {41464,50}, {41480,255}, {41545,50}, {41560,255}, {41568,50}, {41600,255}, {41656,50}, {41680,50}, {41680,255}, {41712,255}, {41760,50}, {41792,255}, {41800,50}, {41832,255}, {41880,50}, {41904,255}, {41912,50}, {41944,255}, {41992,50}, {42024,255}, {42032,50}, {42088,255}, {42104,50}, {42144,50}, {42144,255}, {42168,255}, {42224,50}, {42256,255}, {42288,50}, {42304,255}, {42344,50}, {42368,50}, {42376,255}, {42424,255}, {42456,50}, {42504,50}, {42505,255}, {42528,255}, {42576,50}, {42616,255}, {42624,50}, {42640,255}, {42705,50}, {42728,50}, {42736,255}, {42752,255}, {42816,50}, {42840,50}, {42840,255}, {42920,255}, {42936,50}, {42952,50}, {42992,255}, {43040,50}, {43064,255}, {43120,50}, {43136,255}, {43192,50}, {43200,255}, {43264,50}, {43272,255}, {43320,255}, {43336,50}, {43400,50}, {43400,255}, {43456,255}, {43472,50}, {43520,50}, {43536,255}, {43593,255}, {43600,50}, {43656,50}, {43664,255}, {43728,255}, {43736,50}, {43793,50}, {43808,255}, {43864,50}, {43864,255}, {43928,50}, {43936,255}, {43992,255}, {44008,50}, {44064,50}, {44080,255}, {44128,255}, {44136,50}, {44192,50}, {44209,255}, {44249,255}, {44280,50}, {44328,50}, {44328,255}, {44384,255}, {44409,50}, {44449,50}, {44464,255}, {44512,255}, {44528,50}, {44584,50}, {44593,255}, {44632,255}, {44664,50}, {44712,50}, {44720,255}, {44768,255}, {44793,50}, {44832,50}, {44856,255}, {44912,255}, {44920,50}, {44968,50}, {44992,255}, {45056,50}, {45064,255}, {45112,50}, {45144,255}, {45192,50}, {45208,255}, {45264,50}, {45296,255}, {45344,50}, {45344,255}, {45408,50}, {45416,255}, {45472,255}, {45496,50}, {45536,255}, {45544,50}, {45608,255}, {45616,50}, {45672,50}, {45696,255}, {45736,50}, {45760,255}, {45808,50}, {45832,255}, {45896,50}, {45912,255}, {45960,50}, {46032,50}, {46048,255}, {46096,255}, {46112,50}, {46168,255}, {46232,255}, {46248,50}, {46296,50}, {46328,255}, {46368,50}, {46392,255}, {46432,50}, {46480,255}, {46528,50}, {46544,255}, {46592,50}, {46680,50}, {46744,50}, {52480,255}, {52680,50}, {52808,255}, {52976,255}, {53008,50}, {53104,255}, {53176,50}, {53248,255}, {53304,50}, {53344,255}, {53448,50}, {53544,50}, {53896,255}, {53992,255}, {54096,50}, {54176,255}, {54192,50}, {54288,255}, {54376,50}, {54488,50}, {54912,255}, {55000,255}, {55112,50}, {55200,50}, {56592,255}, {56792,50}, {57056,255}, {57256,50}, {58064,255}, {58264,50}, {58368,255}, {58568,50}, {58856,255}, {59008,255}, {59056,50}, {59208,50}, {59256,255}, {59456,50}, {68031,255}, {68191,255}, {68231,50}, {68391,50}, {68479,255}, {68679,50}, {68727,255}, {68927,50}, {68967,255}, {69151,255}, {69167,50}, {69351,50}, {69440,255}, {69640,50}, {69696,255}, {69896,50}, {71031,255}, {71231,50}, {71279,255}, {71439,255}, {71479,50}, {71559,255}, {71639,50}, {71727,255}, {71759,50}, {71927,50}, {72856,255}, {72991,255}, {73056,50}, {73191,50}, {73855,255}, {74055,50}, {74135,255}, {74335,50}, {75855,255}, {76055,50}, {76095,255}, {76191,255}, {76295,50}, {76303,255}, {76391,50}, {76447,255}, {76503,50}, {76543,255}, {76647,50}, {76703,255}, {76743,50}, {76807,255}, {76903,50}, {76927,255}, {77007,50}, {77039,255}, {77127,50}, {77143,255}, {77231,255}, {77239,50}, {77335,255}, {77343,50}, {77423,255}, {77431,50}, {77535,50}, {77551,255}, {77623,50}, {77647,255}, {77751,50}, {77759,255}, {77847,50}, {77863,255}, {77959,50}, {78063,50}, {78095,255}, {78215,255}, {78295,50}, {78415,50}, {78479,255}, {78615,255}, {78679,50}, {78815,50}, {78815,255}, {79015,50}, {79039,255}, {79239,50}, {79247,255}, {79447,50}, {89495,255}, {89687,255}, {89695,50}, {89847,255}, {89887,50}, {89959,255}, {90047,50}, {90119,255}, {90159,50}, {90279,255}, {90319,50}, {90479,50}, {90583,255}, {90767,255}, {90783,50}, {90967,50}, {90983,255}, {91183,50}, {91183,255}, {91383,50}, {91407,255}, {91607,50}, {92759,255}, {92927,255}, {92959,50}, {93007,255}, {93103,255}, {93127,50}, {93175,255}, {93207,50}, {93255,255}, {93303,50}, {93311,255}, {93375,50}, {93391,255}, {93447,255}, {93455,50}, {93511,50}, {93519,255}, {93559,255}, {93591,50}, {93647,50}, {93647,255}, {93695,255}, {93719,50}, {93759,50}, {93767,255}, {93807,255}, {93847,50}, {93895,50}, {93895,255}, {93927,255}, {93967,50}, {94007,50}, {94015,255}, {94039,255}, {94095,50}, {94119,255}, {94127,50}, {94159,255}, {94215,50}, {94239,50}, {94247,255}, {94287,255}, {94319,50}, {94359,50}, {94367,255}, {94407,255}, {94447,50}, {94487,50}, {94487,255}, {94535,255}, {94567,50}, {94607,50}, {94615,255}, {94655,255}, {94687,50}, {94735,50}, {94735,255}, {94782,255}, {94815,50}, {94855,50}, {94863,255}, {94895,255}, {94935,50}, {94967,255}, {94982,50}, {95007,255}, {95063,50}, {95095,50}, {95095,255}, {95103,255}, {95167,50}, {95207,50}, {95207,255}, {95223,255}, {95295,50}, {95303,50}, {95407,50}, {95423,50}, {109310,255}, {109430,255}, {109478,255}, {109510,50}, {109558,255}, {109590,255}, {109630,50}, {109662,255}, {109678,50}, {109718,255}, {109758,50}, {109782,255}, {109790,50}, {109838,255}, {109862,50}, {109910,255}, {109918,50}, {109958,255}, {109982,50}, {110030,255}, {110038,50}, {110062,255}, {110110,50}, {110142,255}, {110158,50}, {110174,255}, {110230,50}, {110254,255}, {110262,50}, {110286,255}, {110342,50}, {110366,255}, {110374,50}, {110390,255}, {110454,50}, {110486,50}, {110566,50}, {110590,50}, {112742,255}, {112942,50}, {113214,255}, {113358,255}, {113414,50}, {113486,255}, {113558,50}, {113630,255}, {113686,50}, {113830,50}, {113886,255}, {114086,50}, {114774,255}, {114894,255}, {114974,50}, {115078,255}, {115094,50}, {115166,255}, {115278,50}, {115326,255}, {115366,50}, {115462,255}, {115526,50}, {115582,255}, {115662,50}, {115726,255}, {115782,50}, {115838,255}, {115926,50}, {116038,50}, {116238,255}, {116438,50}, {116622,255}, {116742,255}, {116822,50}, {116910,255}, {116942,50}, {117078,255}, {117110,50}, {117206,255}, {117278,50}, {117382,255}, {117406,50}, {117510,255}, {117582,50}, {117638,255}, {117710,50}, {117758,255}, {117838,50}, {117918,255}, {117958,50}, {118070,255}, {118118,50}, {118262,255}, {118270,50}, {118390,255}, {118462,50}, {118590,50}, {118630,255}, {118670,255}, {118806,255}, {118830,50}, {118870,50}, {119006,50}, {119078,255}, {119246,255}, {119278,50}, {119446,50}, {119502,255}, {119702,50}, {119710,255}, {119910,50}, {120998,255}, {121198,50}, {121254,255}, {121454,50}, {121934,255}, {122110,255}, {122134,50}, {122286,255}, {122310,50}, {122366,255}, {122486,50}, {122486,255}, {122566,50}, {122606,255}, {122686,50}, {122790,255}, {122806,50}, {122958,255}, {122990,50}, {123158,50}, {151909,255}, {152109,50}, {152693,255}, {152893,50}, {152933,255}, {153061,255}, {153133,50}, {153261,50}, {153957,255}, {154157,50}, {154237,255}, {154437,50}, {154557,255}, {154701,255}, {154757,50}, {154845,255}, {154901,50}, {154949,255}, {155045,50}, {155109,255}, {155149,50}, {155221,255}, {155309,50}, {155341,255}, {155421,50}, {155453,255}, {155541,50}, {155589,255}, {155653,50}, {155789,50}, {156117,255}, {156317,50}, {156349,255}, {156533,255}, {156549,50}, {156733,50}, {157021,255}, {157221,50}, {157245,255}, {157365,255}, {157445,50}, {157565,50}, {157620,255}, {157725,255}, {157820,50}, {157853,255}, {157925,50}, {157965,255}, {158053,50}, {158165,50}, {158180,255}, {158380,50}, {174004,255}, {174204,50}, {174596,255}, {174796,50}, {175468,255}, {175668,50}, {175821,255}, {176021,50}, {176044,255}, {176244,50}, {176316,255}, {176436,255}, {176516,50}, {176548,255}, {176636,50}, {176668,255}, {176748,50}, {176764,255}, {176868,50}, {176932,255}, {176964,50}, {177132,50}, {177556,255}, {177756,50}, {177884,255}, {178004,255}, {178084,50}, {178196,255}, {178204,50}, {178372,255}, {178396,50}, {178572,50}, {179660,255}, {179860,50}, {192075,255}, {192275,50}, {192531,255}, {192731,50}, {192883,255}, {193004,255}, {193083,50}, {193107,255}, {193187,255}, {193204,50}, {193251,255}, {193307,50}, {193331,255}, {193387,50}, {193395,255}, {193451,50}, {193467,255}, {193523,255}, {193531,50}, {193595,50}, {193595,255}, {193667,50}, {193699,255}, {193723,50}, {193763,255}, {193795,50}, {193835,255}, {193899,50}, {193908,255}, {193963,50}, {193964,255}, {194035,50}, {194035,255}, {194091,255}, {194108,50}, {194164,50}, {194179,255}, {194219,255}, {194235,50}, {194291,50}, {194299,255}, {194315,255}, {194379,50}, {194403,255}, {194419,50}, {194419,255}, {194499,50}, {194515,50}, {194603,50}, {194619,50}, {202275,255}, {202475,50}, {203443,255}, {203523,255}, {203643,50}, {203723,50}, {221795,255}, {221995,50}, {222218,255}, {222394,255}, {222418,50}, {222594,50}, {226154,255}, {226354,50}, {226819,255}, {227010,255}, {227019,50}, {227210,50}, {227242,255}, {227442,50}, {228698,255}, {228898,50}, {229130,255}, {229306,255}, {229330,50}, {229434,255}, {229506,50}, {229610,255}, {229634,50}, {229810,50}, {229834,255}, {230034,50}, {242066,255}, {242082,255}, {242210,255}, {242266,50}, {242282,50}, {242290,255}, {242346,255}, {242410,50}, {242426,255}, {242482,255}, {242490,50}, {242546,50}, {242562,255}, {242626,50}, {242682,50}, {242762,50}, {242906,255}, {242978,255}, {243042,255}, {243106,50}, {243106,255}, {243170,255}, {243178,50}, {243234,255}, {243242,50}, {243290,255}, {243306,50}, {243362,255}, {243370,50}, {243378,255}, {243434,50}, {243466,255}, {243490,50}, {243506,255}, {243562,50}, {243578,50}, {243594,255}, {243634,255}, {243666,50}, {243706,50}, {243706,255}, {243730,255}, {243794,50}, {243818,255}, {243834,50}, {243850,255}, {243906,50}, {243930,50}, {243938,255}, {243962,255}, {244018,50}, {244050,50}, {244058,255}, {244074,255}, {244138,50}, {244162,50}, {244170,255}, {244258,50}, {244274,50}, {244274,255}, {244346,255}, {244370,50}, {244402,255}, {244474,50}, {244490,255}, {244522,255}, {244546,50}, {244586,255}, {244602,50}, {244618,255}, {244690,50}, {244714,255}, {244722,50}, {244786,50}, {244810,255}, {244818,50}, {244842,255}, {244914,50}, {245010,50}, {245042,50}, {247058,255}, {247258,50}, {247650,255}, {247754,255}, {247850,50}, {247882,255}, {247954,50}, {247994,255}, {248082,50}, {248194,50}, {248626,255}, {248801,255}, {248826,50}, {248946,255}, {249001,50}, {249042,255}, {249146,50}, {249178,255}, {249242,50}, {249282,255}, {249378,50}, {249418,255}, {249482,50}, {249570,255}, {249618,50}, {249770,50}, {250274,255}, {250474,50}, {250602,255}, {250745,255}, {250802,50}, {250945,50}, {250954,255}, {251154,50}, {251178,255}, {251378,50}, {251625,255}, {251770,255}, {251825,50}, {251970,50}, {252034,255}, {252234,50}, {252265,255}, {252378,255}, {252465,50}, {252473,255}, {252578,50}, {252586,255}, {252673,50}, {252706,255}, {252786,50}, {252834,255}, {252906,50}, {252977,255}, {253034,50}, {253081,255}, {253177,50}, {253233,255}, {253281,50}, {253385,255}, {253433,50}, {253497,255}, {253585,50}, {253697,50}, {254322,255}, {254522,50}, {255153,255}, {255353,50}
};

// Dashboard LED timing data (timestamp in ms, state)
// States: 0=Off, 1=Idle Blinking, 2=Warning, 3=Alarm
const int dashboardTimingData[][2] = {
  // {0, 1},        // EXAMPLES: Start in Idle Blinking state
  // {10000, 2},    // Switch to Warning at 10 seconds
  // {30000, 3},    // Switch to Alarm at 30 seconds  
  // {60000, 1},    // Back to Idle at 60 seconds
  // {90000, 0},    // Turn off at 90 seconds
  // Add more timing events as needed for your 4:25 performance
  {0, 0},       // Start Off
  {1200, 1},    // Turn on w/ Boot Up Sound
  {31000, 2},   // Maintenance Bay Start
  {37700, 3},   // Where are the brakes?!
  {51349, 0},    // HACK - Clear Reds before Idle 
  {51350, 1},   // Captain rex chills out.
};

// Maestro sequence timing data (timestamp in ms, sequence number)
// This controls when to trigger each Maestro animation sequence
const int maestroTimingData[][2] = {
  {0, 0},        // Start sequence 0 immediately
  // {5000, 0x01},     // Start sequence 1 at 5 seconds
  // {15000, 2},    // Start sequence 2 at 15 seconds
  // {30000, 3},    // Start sequence 3 at 30 seconds
  // Add timing for all your sequences here
  // Continue up to sequence 11 (and eventually 51 when you create them)
};

// Calculate array sizes automatically
const int numMouthEvents = sizeof(mouthTimingData) / sizeof(mouthTimingData[0]);
const int numDashboardEvents = sizeof(dashboardTimingData) / sizeof(dashboardTimingData[0]);  
const int numMaestroEvents = sizeof(maestroTimingData) / sizeof(maestroTimingData[0]);

// ================================================================================
// GLOBAL VARIABLES - SYSTEM STATE TRACKING
// ================================================================================

// LED Arrays for FastLED library
CRGB leds1[LED1_COUNT];      // First LED string (eyes + mouth)
CRGB leds2[LED2_COUNT];      // Second LED string (dashboard)

// Serial communication with Maestro controller
HardwareSerial maestroSerial(1);  // Use hardware serial port 1 for Maestro

// Eye blink control variables
unsigned long lastBlinkTime = 0;        // When the last blink occurred
bool eyesOpen = true;                   // Current state of eyes (true = open)
unsigned long blinkStartTime = 0;       // When current blink started
uint8_t targetEyeBrightness = EYE_BRIGHTNESS;  // Target brightness for eyes

// Mouth LED control variables  
int currentMouthEventIndex = 0;         // Which mouth timing event we're processing
uint8_t targetMouthBrightness = MOUTH_BASELINE;  // Target brightness for mouth
uint8_t currentMouthBrightness = MOUTH_BASELINE; // Current brightness for mouth

// Dashboard LED control variables
int currentDashboardEventIndex = 0;     // Which dashboard timing event we're processing
uint8_t currentDashboardState = 0;      // Current dashboard state (0-3)
unsigned long lastDashboardUpdate = 0;  // Last time dashboard pattern updated
unsigned long nextDashboardBlink = 0;   // When next dashboard blink should occur
bool dashboardLedStates[LED2_COUNT];    // On/off state of each dashboard LED
uint8_t dashboardLedColors[LED2_COUNT]; // Color of each dashboard LED (0=yellow, 1=blue, 2=red)
bool dashboardFlashState = false;       // For warning/alarm flashing
unsigned long lastDashboardFlash = 0;   // Last time warning/alarm flashed

// Maestro control variables
int currentMaestroEventIndex = 0;       // Which maestro timing event we're processing
bool waitingForMaestroResponse = false; // Are we waiting for Maestro to respond
unsigned long maestroCommandSentTime = 0;  // When we sent last command to Maestro

// System timing and control variables
unsigned long performanceStartTime = 0; // When the performance started
bool performanceActive = false;         // Is the performance currently running
bool diagnosticMode = false;            // Are we in diagnostic mode
int countdownSeconds = 0;               // Countdown time before starting (0 = no countdown)
int offsetMilliseconds = 0;             // Timing offset for fine-tuning sync

// ================================================================================
// ARDUINO SETUP FUNCTION - RUNS ONCE AT STARTUP
// ================================================================================

void setup() {
  // Initialize serial communication for debugging and user commands
  Serial.begin(115200);           // Start serial at 115200 baud
  delay(2000);                    // Wait 2 seconds for serial connection to stabilize
  Serial.println("=== ANIMATRONIC CONTROL SYSTEM STARTING ===");
  
  // Initialize LED strips using FastLED library
  FastLED.addLeds<WS2812B, LED1_PIN, GRB>(leds1, LED1_COUNT);  // First LED string
  FastLED.addLeds<WS2812B, LED2_PIN, GRB>(leds2, LED2_COUNT);  // Second LED string
  // FastLED.setBrightness(LED1_BRIGHTNESS);  // Set overall brightness FIX-REMOVED, implemented nscale8 instead
  Serial.println("LED strips initialized");
  
  // Initialize UART communication with Maestro controller
  maestroSerial.begin(MAESTRO_BAUD, SERIAL_8N1, MAESTRO_TX_PIN, MAESTRO_RX_PIN);
  delay(100);  // Small delay for Maestro to initialize
  Serial.println("Maestro serial communication initialized");
  
  // Initialize dashboard LED states to idle blinking pattern
  initializeDashboardLeds();

  randomSeed(analogRead(A0));  // Initialize random number generator for Dashboard Idle  
  
  // Set initial states for all LEDs
  setInitialLedStates();
  
  // Print system information
  printSystemInfo();
  
  // Show ready message and available commands
  Serial.println("\n=== SYSTEM READY ===");
  Serial.println("Commands:");
  Serial.println("  p[countdown] - Start performance (e.g. 'p5' for 5sec countdown)");
  Serial.println("  r - Reset/stop performance");  
  Serial.println("  o[offset] - Set timing offset in ms (e.g. 'o100' for +100ms)");
  Serial.println("  d - Enter diagnostic mode");
  Serial.println("  h - Show help");
  Serial.println();
}

// ================================================================================
// ARDUINO MAIN LOOP - RUNS CONTINUOUSLY
// ================================================================================

void loop() {
  unsigned long currentTime = millis();  // Get current system time
  
  // Handle different system modes
  if (diagnosticMode) {
    // In diagnostic mode - handle diagnostic commands only
    handleDiagnosticMode();
    updateDashboardPattern(currentTime);    // FIX - Update dashboard LED patterns SHOULDNT BE NEEDED, handleDiagnosticMode calls this already
  } else if (performanceActive) {
    // Performance is running - update all systems
    handleEyeBlinks(currentTime);           // Manage eye blinking
    handleMouthTiming(currentTime);         // Update mouth LED based on timing
    handleDashboardTiming(currentTime);     // Update dashboard LEDs based on timing  
    handleMaestroTiming(currentTime);       // Send commands to Maestro based on timing
    fadeMouthLED();                         // Smooth mouth LED brightness changes
    updateDashboardPattern(currentTime);    // Update dashboard LED patterns
  } else {
    // System idle - only handle eye blinks and dashboard idle pattern
    handleEyeBlinks(currentTime);           // Eyes still blink when idle
    updateDashboardPattern(currentTime);    // Dashboard shows idle pattern
  }
  
  // Always check for serial commands from user
  handleSerialCommands();
  
  // Update all LED strips with new values
  FastLED.show();
  
  // Small delay to prevent overwhelming the system
  delay(5);
}

// ================================================================================
// SYSTEM INITIALIZATION FUNCTIONS
// ================================================================================

// Initialize dashboard LEDs to idle blinking state
void initializeDashboardLeds() {
  // Set initial state for dashboard LEDs
  currentDashboardState = 1;  // Start in idle blinking state
  
  // Initialize random pattern for idle blinking
  for (int i = 0; i < LED2_COUNT; i++) {
    if (i != DASH_LED4) {  // Skip LED #4 (keep it off)
      dashboardLedStates[i] = (i < DASH_MIN_ON_LEDS);  // First 3 LEDs start on
      dashboardLedColors[i] = (i % 2);  // Alternate between yellow(0) and blue(1)
    } else {
      dashboardLedStates[i] = false;  // LED #4 always off
      dashboardLedColors[i] = 0;      // Color doesn't matter for off LED
    }
  }
  
  Serial.println("Dashboard LEDs initialized to idle state");
}

// Set initial states for all LEDs
void setInitialLedStates() {
  // Set eye LEDs to initial state (open, light white-blue)
  leds1[EYE1_LED] = CRGB((EYE_RED * targetEyeBrightness) / 255, 
                         (EYE_GREEN * targetEyeBrightness) / 255, 
                         (EYE_BLUE * targetEyeBrightness) / 255);
  leds1[EYE2_LED] = CRGB((EYE_RED * targetEyeBrightness) / 255, 
                         (EYE_GREEN * targetEyeBrightness) / 255, 
                         (EYE_BLUE * targetEyeBrightness) / 255);
  
  // Set mouth LED to baseline state (orange)
  leds1[MOUTH_LED] = CRGB((MOUTH_RED * currentMouthBrightness) / 255,
                          (MOUTH_GREEN * currentMouthBrightness) / 255,
                          (MOUTH_BLUE * currentMouthBrightness) / 255);
  
  // Set dashboard LEDs based on initial states
  updateDashboardLeds();
  
  Serial.println("All LEDs set to initial states");
}

// Print system information at startup
void printSystemInfo() {
  Serial.println("\n=== SYSTEM CONFIGURATION ===");
  Serial.print("LED String 1: "); Serial.print(LED1_COUNT); Serial.print(" LEDs on GPIO "); Serial.println(LED1_PIN);
  Serial.print("LED String 2: "); Serial.print(LED2_COUNT); Serial.print(" LEDs on GPIO "); Serial.println(LED2_PIN);
  Serial.print("Maestro Serial: RX=GPIO"); Serial.print(MAESTRO_RX_PIN); Serial.print(", TX=GPIO"); Serial.println(MAESTRO_TX_PIN);
  Serial.print("Mouth Timing Events: "); Serial.println(numMouthEvents);
  Serial.print("Dashboard Timing Events: "); Serial.println(numDashboardEvents);
  Serial.print("Maestro Timing Events: "); Serial.println(numMaestroEvents);
}

// ================================================================================
// EYE BLINK CONTROL FUNCTIONS
// ================================================================================

// Handle automatic eye blinking
void handleEyeBlinks(unsigned long currentTime) {
  if (eyesOpen) {
    // Eyes are currently open - check if it's time to blink
    if (currentTime - lastBlinkTime >= BLINK_INTERVAL) {
      startEyeBlink(currentTime);  // Start a new blink
    }
  } else {
    // Eyes are currently closed - check if blink should end
    if (currentTime - blinkStartTime >= BLINK_DURATION) {
      endEyeBlink(currentTime);    // End the current blink
    }
  }
}

// Start an eye blink (close eyes)
void startEyeBlink(unsigned long currentTime) {
  leds1[EYE1_LED] = CRGB::Black;    // Turn off first eye LED
  leds1[EYE2_LED] = CRGB::Black;    // Turn off second eye LED
  eyesOpen = false;                 // Mark eyes as closed
  blinkStartTime = currentTime;     // Record when blink started
  // Serial.println("Eyes: BLINK"); // FIX HCUSTOM - Commented out, unnecessary diagnostic info
}

// End an eye blink (open eyes)  
void endEyeBlink(unsigned long currentTime) {
  // Restore eye LEDs to normal brightness and color
  leds1[EYE1_LED] = CRGB((EYE_RED * targetEyeBrightness) / 255,
                         (EYE_GREEN * targetEyeBrightness) / 255,
                         (EYE_BLUE * targetEyeBrightness) / 255);
  leds1[EYE2_LED] = CRGB((EYE_RED * targetEyeBrightness) / 255,
                         (EYE_GREEN * targetEyeBrightness) / 255,
                         (EYE_BLUE * targetEyeBrightness) / 255);
  eyesOpen = true;                  // Mark eyes as open
  lastBlinkTime = currentTime;      // Update last blink time for next blink
  // Serial.println("Eyes: OPEN"); // FIX HCUSTOM - Commented out, unnecessary diagnostic info
}

// ================================================================================
// MOUTH LED CONTROL FUNCTIONS  
// ================================================================================

// Handle mouth LED timing based on embedded timing data
void handleMouthTiming(unsigned long currentTime) {
  // Calculate elapsed time since performance started (with offset)
  unsigned long elapsedTime = currentTime - performanceStartTime + offsetMilliseconds;
  
  // Check if we need to process the next mouth timing event
  if (currentMouthEventIndex < numMouthEvents) {
    unsigned long eventTime = mouthTimingData[currentMouthEventIndex][0];    // Get event timestamp
    uint8_t eventBrightness = mouthTimingData[currentMouthEventIndex][1];    // Get event brightness
    
    // Check if it's time for this event
    if (elapsedTime >= eventTime) {
      targetMouthBrightness = eventBrightness;  // Set new target brightness
      
      // Print debug information
      Serial.print("Mouth Event ");
      Serial.print(currentMouthEventIndex);
      Serial.print(": ");
      Serial.print(eventTime);
      Serial.print("ms -> brightness ");
      Serial.println(eventBrightness);
      
      currentMouthEventIndex++;  // Move to next event
    }
  } else if (performanceActive) {
    // All mouth timing events completed
    targetMouthBrightness = MOUTH_BASELINE;  // Return to baseline
    Serial.println("Mouth timing sequence complete");
  }
}

// Gradually fade mouth LED towards target brightness
void fadeMouthLED() {
  // Calculate difference between current and target brightness
  int brightnessDifference = (int)targetMouthBrightness - (int)currentMouthBrightness;
  
  // Only fade if the difference is significant (more than 1 level)
  if (abs(brightnessDifference) > 1) {
    if (brightnessDifference > 0) {
      // Need to get brighter - increase brightness
      currentMouthBrightness = min(255, (int)currentMouthBrightness + FADE_SPEED);
    } else {
      // Need to get dimmer - decrease brightness  
      currentMouthBrightness = max(0, (int)currentMouthBrightness - FADE_SPEED);
    }
    
    // Apply new brightness to mouth LED using orange color
    leds1[MOUTH_LED] = CRGB((MOUTH_RED * currentMouthBrightness) / 255,
                            (MOUTH_GREEN * currentMouthBrightness) / 255, 
                            (MOUTH_BLUE * currentMouthBrightness) / 255);
  }
}

// ================================================================================
// DASHBOARD LED CONTROL FUNCTIONS
// ================================================================================

// Handle dashboard LED timing based on embedded timing data
void handleDashboardTiming(unsigned long currentTime) {
  // Calculate elapsed time since performance started (with offset)
  unsigned long elapsedTime = currentTime - performanceStartTime + offsetMilliseconds;
  
  // Check if we need to process the next dashboard timing event
  if (currentDashboardEventIndex < numDashboardEvents) {
    unsigned long eventTime = dashboardTimingData[currentDashboardEventIndex][0];  // Get event timestamp
    uint8_t newState = dashboardTimingData[currentDashboardEventIndex][1];          // Get new state
    
    // Check if it's time for this event
    if (elapsedTime >= eventTime) {
      currentDashboardState = newState;  // Change to new state
      
      // Print debug information
      Serial.print("Dashboard Event ");
      Serial.print(currentDashboardEventIndex);
      Serial.print(": ");
      Serial.print(eventTime);
      Serial.print("ms -> state ");
      Serial.println(newState);
      
      currentDashboardEventIndex++;  // Move to next event
      lastDashboardUpdate = currentTime;  // Reset timing for new state
    }
  }
}

// Update dashboard LED pattern based on current state
void updateDashboardPattern(unsigned long currentTime) {
  switch(currentDashboardState) {
    case 0:  // Off State
      updateDashboardOff();
      break;
    case 1:  // Idle Blinking State  
      updateDashboardIdle(currentTime);
      break;
    case 2:  // Warning State
      updateDashboardWarning(currentTime);
      break;
    case 3:  // Alarm State
      updateDashboardAlarm(currentTime);
      break;
    default:
      Serial.println("ERROR: Unknown dashboard state");
      currentDashboardState = 1;  // Default to idle if unknown state
      break;
  }
  
  // Update the actual LED colors based on current states
  updateDashboardLeds();
}

// Dashboard Off State - all LEDs off
void updateDashboardOff() {
  // Turn off all dashboard LEDs
  for (int i = 0; i < LED2_COUNT; i++) {
    dashboardLedStates[i] = false;
  }
}

// Dashboard Idle Blinking State - random yellow/blue blinking
void updateDashboardIdle(unsigned long currentTime) {
  // Check if it's time for next blink event
  if (currentTime >= nextDashboardBlink) {
    // Count how many LEDs are currently on (excluding LED #4)
    int ledsOn = 0;
    for (int i = 0; i < LED2_COUNT; i++) {
      if (i != DASH_LED4 && dashboardLedStates[i]) {
        ledsOn++;
      }
    }
    
    // Find a random LED to change state (excluding LED #4)
    int targetLed = random(0, LED2_COUNT);
    while (targetLed == DASH_LED4) {
      targetLed = random(0, LED2_COUNT);  // Keep picking until we don't get LED #4
    }
    
    // Decide whether to turn LED on or off
    bool currentState = dashboardLedStates[targetLed];
    
    if (!currentState) {
      // LED is off - turn it on and maybe change color
      dashboardLedStates[targetLed] = true;
      dashboardLedColors[targetLed] = random(0, 2);  // Random color: 0=yellow, 1=blue
    } else if (ledsOn > DASH_MIN_ON_LEDS) {
      // LED is on and we have enough LEDs on - can turn it off
      dashboardLedStates[targetLed] = false;
    } else {
      // LED is on but we need to keep minimum LEDs on - just change color
      dashboardLedColors[targetLed] = random(0, 2);  // Random color: 0=yellow, 1=blue
    }
    
    // Schedule next blink event
    nextDashboardBlink = currentTime + random(DASH_IDLE_MIN_TIME, DASH_IDLE_MAX_TIME);
  }
}

// Dashboard Warning State - like idle but flash red periodically CHANGED TO RED FLASH HCUSTOM
void updateDashboardWarning(unsigned long currentTime) {
  // // First update the idle blinking pattern HCUSTOM REMOVED
  // updateDashboardIdle(currentTime);

  // Then check if it's time to flash red
  if (currentTime - lastDashboardFlash >= DASH_WARNING_INTERVAL) {
    dashboardFlashState = !dashboardFlashState;  // Toggle flash state
    lastDashboardFlash = currentTime;
    
    if (dashboardFlashState) {
      // Flash all LEDs red (except LED #4)
      for (int i = 0; i < LED2_COUNT; i++) {
        if (i != DASH_LED4) {
          dashboardLedStates[i] = true;
          dashboardLedColors[i] = 2;  // Red color
        }
      }
    } else {  // HCUSTOM Toggle to Off State
      updateDashboardOff();
    }
    // If flash state is false, the idle pattern continues normally
  }
}

// Dashboard Alarm State - like warning but flash red more rapidly
void updateDashboardAlarm(unsigned long currentTime) {
  // // First update the idle blinking pattern
  // updateDashboardIdle(currentTime);
  
  // Then check if it's time to flash red (more frequently than warning)
  if (currentTime - lastDashboardFlash >= DASH_ALARM_INTERVAL) {
    dashboardFlashState = !dashboardFlashState;  // Toggle flash state
    lastDashboardFlash = currentTime;
    
    if (dashboardFlashState) {
      // Flash all LEDs red (except LED #4)
      for (int i = 0; i < LED2_COUNT; i++) {
        if (i != DASH_LED4) {
          dashboardLedStates[i] = true;
          dashboardLedColors[i] = 2;  // Red color
        }
      }
    } else {  // HCUSTOM Toggle to Off State
      updateDashboardOff();
    }
    // If flash state is false, the idle pattern continues normally
  }
}

// Update physical dashboard LEDs based on current states and colors
void updateDashboardLeds() {
  for (int i = 0; i < LED2_COUNT; i++) {
    if (dashboardLedStates[i] && i != DASH_LED4) {  // LED is on and not LED #4
      // Set color based on dashboardLedColors array
      switch(dashboardLedColors[i]) {
        case 0:  // Yellow
          leds2[i] = CRGB(YELLOW_RED, YELLOW_GREEN, YELLOW_BLUE);
          break;
        case 1:  // Blue
          leds2[i] = CRGB(BLUE_RED, BLUE_GREEN, BLUE_BLUE);
          break;
        case 2:  // Red
          leds2[i] = CRGB(RED_RED, RED_GREEN, RED_BLUE);
          break;
        default: // Default to yellow if unknown color
          leds2[i] = CRGB(YELLOW_RED, YELLOW_GREEN, YELLOW_BLUE);
          break;
      }
      // Scale this LED's brightness individually
      leds2[i].nscale8(LED2_BRIGHTNESS);
    } else {
      // LED is off or is LED #4 (always off)
      leds2[i] = CRGB::Black;
    }
  }
}

// ================================================================================
// MAESTRO CONTROL FUNCTIONS
// ================================================================================

// Handle Maestro timing based on embedded timing data
void handleMaestroTiming(unsigned long currentTime) {
  // Calculate elapsed time since performance started (with offset)
  unsigned long elapsedTime = currentTime - performanceStartTime + offsetMilliseconds;
  
  // Check if we need to process the next maestro timing event
  if (currentMaestroEventIndex < numMaestroEvents) {
    unsigned long eventTime = maestroTimingData[currentMaestroEventIndex][0];    // Get event timestamp
    uint8_t sequenceNumber = maestroTimingData[currentMaestroEventIndex][1];     // Get sequence number
    
    // Check if it's time for this event
    if (elapsedTime >= eventTime) {
      startMaestroSequence(sequenceNumber);  // Start the sequence
      
      // Print debug information
      Serial.print("Maestro Event ");
      Serial.print(currentMaestroEventIndex);
      Serial.print(": ");
      Serial.print(eventTime);
      Serial.print("ms -> sequence ");
      Serial.println(sequenceNumber);
      
      currentMaestroEventIndex++;  // Move to next event
    }
  } else if (performanceActive) {
    // All maestro timing events completed
    // Serial.println("Maestro timing sequence complete");  // FIX - commented out to prevent serial monitor from being flooded with messages
  }
}

// Start a specific sequence on the Maestro controller
void startMaestroSequence(uint8_t sequenceNumber) {
  // Send command to Maestro to start sequence
  // Maestro protocol: 0xAA, device number, 0x27, sequence number
  maestroSerial.write(0xAA);                    // Command header
  maestroSerial.write(MAESTRO_DEVICE_NUMBER);   // Device number  
  maestroSerial.write(0x27);                    // "Start Script" command
  maestroSerial.write(sequenceNumber);          // Which sequence to start
  
  waitingForMaestroResponse = true;             // Mark that we're waiting for response
  maestroCommandSentTime = millis();            // Record when command was sent
  
  Serial.print("Maestro: Starting sequence ");
  Serial.println(sequenceNumber);
}

// Check for responses from Maestro controller
void checkMaestroResponse() {
  if (waitingForMaestroResponse && maestroSerial.available()) {
    // Read response from Maestro
    uint8_t response = maestroSerial.read();
    
    // Process the response (Maestro sends 0x00 for successful command)
    if (response == 0x00) {
      Serial.println("Maestro: Sequence started successfully");
    } else {
      Serial.print("Maestro: Error response: ");
      Serial.println(response, HEX);
    }
    
    waitingForMaestroResponse = false;  // No longer waiting for response
  }
  
  // Check for timeout (if no response after 1 second)
  if (waitingForMaestroResponse && (millis() - maestroCommandSentTime > 1000)) {
    Serial.println("Maestro: Command timeout - no response");
    waitingForMaestroResponse = false;
  }
}

// Stop all Maestro sequences
void stopMaestroSequences() {
  // Send command to stop all scripts
  maestroSerial.write(0xAA);                    // Command header
  maestroSerial.write(MAESTRO_DEVICE_NUMBER);   // Device number
  maestroSerial.write(0x24);                    // "Stop Script" command
  
  Serial.println("Maestro: All sequences stopped");
}

// ================================================================================
// SERIAL COMMAND HANDLING FUNCTIONS
// ================================================================================

// Handle commands received from serial monitor
void handleSerialCommands() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');  // Read command until newline
    command.trim();                                   // Remove whitespace
    
    if (command.length() == 0) return;              // Ignore empty commands
    
    char firstChar = command.charAt(0);             // Get first character
    
    if (diagnosticMode) {
      // In diagnostic mode - handle diagnostic commands
      handleDiagnosticCommand(command);
    } else {
      // In normal mode - handle main commands
      handleMainCommand(command, firstChar);
    }
  }
}

// Handle main system commands (not in diagnostic mode)
void handleMainCommand(String command, char firstChar) {
  switch(firstChar) {
    case 'p':
    case 'P':
      // Start performance with optional countdown
      handleStartCommand(command);
      break;
      
    case 'r':
    case 'R':
      // Reset/stop performance
      handleResetCommand();
      break;
      
    case 'o':
    case 'O':
      // Set timing offset
      handleOffsetCommand(command);
      break;
      
    case 'd':
    case 'D':
      // Enter diagnostic mode
      enterDiagnosticMode();
      break;
      
    case 'h':
    case 'H':
      // Show help
      showMainHelp();
      break;
      
    default:
      Serial.print("Unknown command: ");
      Serial.println(command);
      Serial.println("Type 'h' for help");
      break;
  }
}

// Handle start performance command with optional countdown
void handleStartCommand(String command) {
  if (performanceActive) {
    Serial.println("Performance already running! Use 'r' to reset first.");
    return;
  }
  
  // Parse countdown time if provided (e.g., "p5" for 5 second countdown)
  countdownSeconds = 0;
  if (command.length() > 1) {
    String numberPart = command.substring(1);  // Get everything after 'p'
    countdownSeconds = numberPart.toInt();     // Convert to integer
    
    // Limit countdown to reasonable range
    if (countdownSeconds < 0) countdownSeconds = 0;
    if (countdownSeconds > MAX_COUNTDOWN_SECONDS) {
      countdownSeconds = MAX_COUNTDOWN_SECONDS;
      Serial.print("Countdown limited to ");
      Serial.print(MAX_COUNTDOWN_SECONDS);
      Serial.println(" seconds");
    }
  }
  
  startPerformance();  // Start the performance
}

// Handle reset/stop performance command
void handleResetCommand() {
  if (!performanceActive) {
    Serial.println("No performance running to reset.");
    return;
  }
  
  stopPerformance();  // Stop the performance
}

// Handle timing offset command
void handleOffsetCommand(String command) {
  if (command.length() > 1) {
    String numberPart = command.substring(1);  // Get everything after 'o'
    offsetMilliseconds = numberPart.toInt();   // Convert to integer
    
    Serial.print("Timing offset set to ");
    Serial.print(offsetMilliseconds);
    Serial.println(" milliseconds");
  } else {
    Serial.print("Current timing offset: ");
    Serial.print(offsetMilliseconds);
    Serial.println(" milliseconds");
  }
}

// Show main help information
void showMainHelp() {
  Serial.println("\n=== MAIN COMMANDS ===");
  Serial.println("p[countdown] - Start performance");
  Serial.println("               Examples: 'p' (no countdown), 'p5' (5 sec countdown)");
  Serial.println("r            - Reset/stop performance");
  Serial.println("o[offset]    - Set timing offset in milliseconds");
  Serial.println("               Examples: 'o100' (+100ms), 'o-50' (-50ms)");
  Serial.println("d            - Enter diagnostic mode");
  Serial.println("h            - Show this help");
  Serial.println();
}

// ================================================================================
// PERFORMANCE CONTROL FUNCTIONS
// ================================================================================

// Start the main performance
void startPerformance() {
  Serial.println("=== STARTING PERFORMANCE ===");
  
  // Show countdown if requested
  if (countdownSeconds > 0) {
    performCountdown();
  }
  
  // Reset all timing indices to start from beginning
  currentMouthEventIndex = 0;
  currentDashboardEventIndex = 0;
  currentMaestroEventIndex = 0;
  
  // Record performance start time
  performanceStartTime = millis();
  performanceActive = true;
  
  // Reset mouth LED to baseline
  targetMouthBrightness = MOUTH_BASELINE;
  currentMouthBrightness = MOUTH_BASELINE;
  
  // Start dashboard in appropriate state
  currentDashboardState = (numDashboardEvents > 0) ? dashboardTimingData[0][1] : 1;
  
  Serial.println("Performance started! Play your video now.");
  Serial.print("Total events: Mouth=");
  Serial.print(numMouthEvents);
  Serial.print(", Dashboard=");
  Serial.print(numDashboardEvents);
  Serial.print(", Maestro=");
  Serial.println(numMaestroEvents);
}

// Stop the performance and reset to idle state
void stopPerformance() {
  Serial.println("=== STOPPING PERFORMANCE ===");
  
  performanceActive = false;  // Mark performance as stopped
  
  // Stop Maestro sequences
  stopMaestroSequences();
  
  // Reset all timing indices
  currentMouthEventIndex = 0;
  currentDashboardEventIndex = 0;
  currentMaestroEventIndex = 0;
  
  // Reset mouth LED to baseline
  targetMouthBrightness = MOUTH_BASELINE;
  currentMouthBrightness = MOUTH_BASELINE;
  leds1[MOUTH_LED] = CRGB((MOUTH_RED * currentMouthBrightness) / 255,
                          (MOUTH_GREEN * currentMouthBrightness) / 255,
                          (MOUTH_BLUE * currentMouthBrightness) / 255);
  
  // Reset dashboard to idle state
  currentDashboardState = 1;  // Idle blinking
  initializeDashboardLeds();
  
  Serial.println("Performance stopped. Ready to start again with 'p'");
}

// Perform countdown before starting performance
void performCountdown() {
  Serial.print("Starting countdown: ");
  Serial.println(countdownSeconds);
  
  // Count down in 0.01 second increments for smooth display
  int totalTicks = countdownSeconds * 100;  // Convert seconds to 0.01 second ticks
  
  for (int tick = totalTicks; tick > 0; tick--) {
    // Calculate current time remaining  
    float timeRemaining = (float)tick / 100.0;
    
    // Print time with 2 decimal places
    Serial.print("Countdown: ");
    Serial.print(timeRemaining, 2);
    Serial.println(" seconds");
    
    delay(COUNTDOWN_RESOLUTION);  // Wait 0.01 seconds (10ms)
  }
  
  Serial.println("GO!");
}

// ================================================================================
// DIAGNOSTIC MODE FUNCTIONS
// ================================================================================

// Handle diagnostic mode operations
void handleDiagnosticMode() {
  // In diagnostic mode, we still need to update LEDs but not timing
  updateDashboardPattern(millis());  // Keep dashboard pattern running
  FastLED.show();  // FIX - Add this line if updateDashboardPattern FastLED.show isnt working 
  
  // Check for Maestro responses if waiting
  checkMaestroResponse();
}

// Enter diagnostic mode
void enterDiagnosticMode() {
  diagnosticMode = true;
  
  // Stop performance if running
  if (performanceActive) {
    stopPerformance();
  }
  
  Serial.println("\n=== DIAGNOSTIC MODE ACTIVE ===");
  Serial.println("Type 'h' for diagnostic commands or 'x' to exit");
  Serial.println("\n=== DIAGNOSTIC COMMANDS ===");
  Serial.println("h           - Show this help");
  Serial.println("x           - Exit diagnostic mode");
  Serial.println("e           - Test eye LEDs (blink)");
  Serial.println("m[level]    - Test mouth LED brightness (0-255)");
  Serial.println("              Example: 'm255' (full bright), 'm50' (dim)");
  Serial.println("i           - Test dashboard idle state");
  Serial.println("w           - Test dashboard warning state");
  Serial.println("a           - Test dashboard alarm state");
  Serial.println("o           - Test dashboard off state");
  Serial.println("s[seq]      - Test maestro sequence");
  Serial.println("              Example: 's0' (sequence 0), 's5' (sequence 5)");
  Serial.println("p[servo][pos] - Set servo position");
  Serial.println("              Example: 'p0128' (servo 0 to position 128)");
  Serial.println();
}

// Exit diagnostic mode
void exitDiagnosticMode() {
  diagnosticMode = false;
  
  // Reset all systems to normal state
  setInitialLedStates();
  
  Serial.println("=== EXITED DIAGNOSTIC MODE ===");
  Serial.println("Returned to normal operation");
}

// Handle diagnostic commands
void handleDiagnosticCommand(String command) {
  char firstChar = command.charAt(0);
  
  switch(firstChar) {
    case 'h':
    case 'H':
      showDiagnosticHelp();
      break;
      
    case 'x':
    case 'X':
      exitDiagnosticMode();
      break;
      
    case 'e':
    case 'E':
      testEyeLeds();
      break;
      
    case 'm':
    case 'M':
      testMouthLed(command);
      break;
      
    case 'i':
      testDashboardIdle();
      break;
      
    case 'w':
      testDashboardWarning();
      break;
      
    case 'a':
      testDashboardAlarm();
      break;
      
    case 'o':
      testDashboardOff();
      break;
      
    case 's':
    case 'S':
      testMaestroSequence(command);
      break;
      
    case 'p':
    case 'P':
      testServoPosition(command);
      break;
      
    default:
      Serial.print("Unknown diagnostic command: ");
      Serial.println(command);
      Serial.println("Type 'h' for help");
      break;
  }
}

// Show diagnostic help
void showDiagnosticHelp() {
  Serial.println("\n=== DIAGNOSTIC COMMANDS ===");
  Serial.println("h           - Show this help");
  Serial.println("x           - Exit diagnostic mode");
  Serial.println("e           - Test eye LEDs (blink)");
  Serial.println("m[level]    - Test mouth LED brightness (0-255)");
  Serial.println("              Example: 'm255' (full bright), 'm50' (dim)");
  Serial.println("i           - Test dashboard idle state");
  Serial.println("w           - Test dashboard warning state");
  Serial.println("a           - Test dashboard alarm state");
  Serial.println("o           - Test dashboard off state");
  Serial.println("s[seq]      - Test maestro sequence");
  Serial.println("              Example: 's0' (sequence 0), 's5' (sequence 5)");
  Serial.println("p[servo][pos] - Set servo position");
  Serial.println("              Example: 'p0128' (servo 0 to position 128)");
  Serial.println();
}

// Test eye LEDs by forcing a blink
void testEyeLeds() {
  Serial.println("Testing eye LEDs - forcing blink");
  
  // Force immediate blink
  startEyeBlink(millis());
  FastLED.show();
  
  delay(BLINK_DURATION + 100);  // Wait for blink to complete
  
  // Force eyes open
  endEyeBlink(millis());
  FastLED.show();
  
  Serial.println("Eye LED test complete");
}

// Test mouth LED at specific brightness
void testMouthLed(String command) {
  uint8_t testBrightness = MOUTH_MAX;  // Default to maximum
  
  // Parse brightness level if provided
  if (command.length() > 1) {
    String numberPart = command.substring(1);
    int brightness = numberPart.toInt();
    if (brightness >= 0 && brightness <= 255) {
      testBrightness = brightness;
    }
  }
  
  Serial.print("Testing mouth LED at brightness ");
  Serial.println(testBrightness);
  
  // Set mouth LED to test brightness
  leds1[MOUTH_LED] = CRGB((MOUTH_RED * testBrightness) / 255,
                          (MOUTH_GREEN * testBrightness) / 255,
                          (MOUTH_BLUE * testBrightness) / 255);
  FastLED.show();
  
  Serial.println("Mouth LED test active - send another command to change");
}

// Test dashboard in idle state
void testDashboardIdle() {
  Serial.println("Testing dashboard - Idle state");
  currentDashboardState = 1;
  initializeDashboardLeds();
  Serial.println("Dashboard set to idle blinking");
}

// Test dashboard in warning state  
void testDashboardWarning() {
  Serial.println("Testing dashboard - Warning state");
  currentDashboardState = 2;
  lastDashboardFlash = millis() - DASH_WARNING_INTERVAL;  // Force immediate flash
  Serial.println("Dashboard set to warning state");
}

// Test dashboard in alarm state
void testDashboardAlarm() {
  Serial.println("Testing dashboard - Alarm state");
  currentDashboardState = 3;
  lastDashboardFlash = millis() - DASH_ALARM_INTERVAL;  // Force immediate flash
  Serial.println("Dashboard set to alarm state");
}

// Test dashboard in off state
void testDashboardOff() {
  Serial.println("Testing dashboard - Off state");
  currentDashboardState = 0;
  updateDashboardOff();
  updateDashboardLeds();
  FastLED.show();
  Serial.println("Dashboard set to off state");
}

// Test specific Maestro sequence
void testMaestroSequence(String command) {
  uint8_t sequenceNumber = 0;  // Default to sequence 0
  
  // Parse sequence number if provided
  if (command.length() > 1) {
    String numberPart = command.substring(1);
    int seqNum = numberPart.toInt();
    if (seqNum >= 0 && seqNum <= 255) {
      sequenceNumber = seqNum;
    }
  }
  
  Serial.print("Testing Maestro sequence ");
  Serial.println(sequenceNumber);
  
  startMaestroSequence(sequenceNumber);
  Serial.println("Sequence command sent to Maestro");
}

// Test servo position (move individual servo)
void testServoPosition(String command) {
  // Parse servo number and position from command
  // Format: p[servo][position] e.g., "p0128" = servo 0 to position 128
  
  if (command.length() < 4) {
    Serial.println("Invalid format. Use: p[servo][position]");
    Serial.println("Example: 'p0128' (servo 0 to position 128)");
    return;
  }
  
  // Extract servo number (1 digit) and position (remaining digits)
  int servoNumber = command.substring(1, 2).toInt();
  int position = command.substring(2).toInt();
  
  // Validate ranges
  if (servoNumber < 0 || servoNumber > 11) {
    Serial.println("Servo number must be 0-11");
    return;
  }
  
  if (position < 500 || position > 2500) {
    Serial.println("Position must be 500-2500 (microseconds)");
    return;
  }
  
  Serial.print("Moving servo ");
  Serial.print(servoNumber);
  Serial.print(" to position ");
  Serial.println(position);
  
  // Send position command to Maestro
  // Maestro protocol: 0xAA, device number, 0x04, servo number, position low, position high
  uint16_t pos = position * 4;  // Convert to quarter-microseconds
  
  maestroSerial.write(0xAA);                    // Command header
  maestroSerial.write(MAESTRO_DEVICE_NUMBER);   // Device number
  maestroSerial.write(0x04);                    // "Set Target" command
  maestroSerial.write(servoNumber);             // Servo number
  maestroSerial.write(pos & 0x7F);              // Position bits 0-6
  maestroSerial.write((pos >> 7) & 0x7F);       // Position bits 7-13
  
  Serial.println("Servo position command sent");
}

// ================================================================================
// END OF CODE
// ================================================================================