// ================================================================================
// 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
// ================================================================================