#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
#include <Wire.h>
#include <MPU6050.h>
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
// ================================
// ENHANCED GHOSTBUSTERS PROTON PACK v4.0
// Features: EEPROM Storage, Accelerometer, Authentic MP3 Sounds
// ================================
// ESP32 PIN DEFINITIONS
#define ION_ARM_SWITCH 16 // Main pack power
#define WAND_ACTIVATE 17 // Wand activation trigger
#define FIRE_TRIGGER 15 // Proton stream trigger
#define MANUAL_VENT_BTN 2 // Emergency thermal vent
#define SAFETY_INTERLOCK 5 // Critical safety system
#define PACK_DIAGNOSTIC 14 // Diagnostic/status check
#define SMOKE_PIN 21 // Thermal vent smoke
// MP3 PLAYER PINS (DFPlayer Mini)
#define MP3_RX_PIN 4 // Connect to DFPlayer TX
#define MP3_TX_PIN 22 // Connect to DFPlayer RX
#define MP3_BUSY_PIN 23 // Connect to DFPlayer BUSY
// RUMBLE MOTOR CONTROL (ESP32 Core 3.x API)
#define RUMBLE_MOSFET_PIN 12 // PWM pin to control MOSFET gate
#define RUMBLE_PWM_FREQ 1000 // 1kHz PWM frequency
#define RUMBLE_PWM_RESOLUTION 8 // 8-bit resolution (0-255)
// ACCELEROMETER PINS (MPU6050) - Using available pins
#define SDA_PIN 32 // I2C Data (available pin)
#define SCL_PIN 13 // I2C Clock (available pin)
// LED Outputs
#define POWER_CELL_PIN 18 // 15-segment power cell
#define CYCLOTRON_PIN 19 // 40-segment cyclotron ring
#define WAND_TIP_PIN 25 // Wand focusing tip (moved from 22)
#define WAND_SIDE_PIN 26 // Thermal warning strip (moved from 23)
#define WAND_BARGRAPH 27 // Power level bargraph (moved from 25)
#define WAND_STATUS_PIN 33 // Status indicator (moved from 26)
// LED Counts
#define POWER_CELL_LEDS 15
#define CYCLOTRON_LEDS 40
#define WAND_TIP_LEDS 6
#define WAND_SIDE_LEDS 8
#define WAND_BARGRAPH_LEDS 10
#define WAND_STATUS_LEDS 1
// PHYSICS CONSTANTS
#define AMBIENT_TEMP 20.0
#define MAX_SAFE_TEMP 85.0
#define CRITICAL_TEMP 95.0
#define EMERGENCY_TEMP 105.0
#define THERMAL_MASS 0.85
#define HEAT_DISSIPATION 0.15
#define FIRING_HEAT_RATE 2.8
#define VENT_COOLING_RATE 4.5
// POWER SYSTEM
#define PACK_VOLTAGE 14.8
#define MIN_VOLTAGE 12.0
#define VOLTAGE_DROP_RATE 0.002
#define CHARGING_RECOVERY 0.001
// CYCLOTRON PHYSICS
#define CYCLOTRON_STARTUP_TIME 4000
#define CYCLOTRON_MIN_RPM 120
#define CYCLOTRON_MAX_RPM 1800
#define CYCLOTRON_ACCELERATION 0.25
// CYCLOTRON HEAT VISUALIZATION
#define CYCLOTRON_COOL_TEMP 25.0
#define CYCLOTRON_WARM_TEMP 45.0
#define CYCLOTRON_HOT_TEMP 65.0
#define CYCLOTRON_BLAZING_TEMP 85.0
#define CYCLOTRON_HEAT_RATE 0.8
#define CYCLOTRON_COOL_RATE 0.3
#define CYCLOTRON_RPM_HEAT_FACTOR 0.0008
// PROTON STREAM PHYSICS
#define STREAM_CHARGE_TIME 1500
#define STREAM_MAX_DURATION 12000
#define STREAM_COOLDOWN_RATIO 2.5
#define BEAM_INSTABILITY_CHANCE 15
// CROSSING STREAMS DETECTION
#define CROSSING_ANGLE_THRESHOLD 30.0 // Degrees for crossing detection
#define CROSSING_DISTANCE_MAX 2.0 // Max distance in meters
#define CROSSING_TIME_MIN 1000 // Min time for crossing detection
// TESTING MODE
#define TESTING_MODE true
// EEPROM ADDRESSES
#define EEPROM_SIZE 512
#define ADDR_EQUIPMENT_HEALTH 0 // Float (4 bytes)
#define ADDR_OPERATING_HOURS 4 // Unsigned long (4 bytes)
#define ADDR_TOTAL_SHOTS 8 // Unsigned long (4 bytes)
#define ADDR_MAINTENANCE_FLAG 12 // Bool (1 byte)
#define ADDR_LAST_SAVE_TIME 13 // Unsigned long (4 bytes)
#define ADDR_CHECKSUM 17 // Byte (1 byte)
// MP3 TRACK NUMBERS (organize your SD card with these numbers)
#define TRACK_STARTUP 1 // Proton pack startup
#define TRACK_ION_ARM_CHARGE 2 // Ion arm charging
#define TRACK_WAND_SYNC 3 // Wand synchronization
#define TRACK_READY 4 // System ready
#define TRACK_STREAM_CHARGE 5 // Stream charging
#define TRACK_STREAM_FIRE 6 // Proton stream firing
#define TRACK_STREAM_LOOP 7 // Continuous firing loop
#define TRACK_VENT_MANUAL 8 // Manual thermal vent
#define TRACK_VENT_EMERGENCY 9 // Emergency vent
#define TRACK_CROSSING_WARNING 10 // "Don't cross the streams!"
#define TRACK_CROSSING_EXPLOSION 11 // Crossing streams explosion
#define TRACK_SHUTDOWN 12 // System shutdown
#define TRACK_MALFUNCTION 13 // Equipment malfunction
#define TRACK_LOW_BATTERY 14 // Low battery warning
// UPDATE INTERVALS (ms)
#define PHYSICS_UPDATE_INTERVAL 50
#define VISUAL_UPDATE_INTERVAL 33
#define AUDIO_UPDATE_INTERVAL 25
#define INPUT_UPDATE_INTERVAL 10
#define STATUS_UPDATE_INTERVAL 2000
#define SAFETY_CHECK_INTERVAL 250
#define EEPROM_SAVE_INTERVAL 30000 // Save every 30 seconds
#define ACCELEROMETER_UPDATE_INTERVAL 50 // Check accelerometer every 50ms
#define RUMBLE_UPDATE_INTERVAL 20 // Update rumble every 20ms for smooth haptics
// ================================
// HARDWARE INITIALIZATION
// ================================
// NeoPixel strips
Adafruit_NeoPixel powerCell(POWER_CELL_LEDS, POWER_CELL_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel cyclotron(CYCLOTRON_LEDS, CYCLOTRON_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel wandTip(WAND_TIP_LEDS, WAND_TIP_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel wandSide(WAND_SIDE_LEDS, WAND_SIDE_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel wandBargraph(WAND_BARGRAPH_LEDS, WAND_BARGRAPH, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel wandStatus(WAND_STATUS_LEDS, WAND_STATUS_PIN, NEO_GRB + NEO_KHZ800);
// MP3 Player
SoftwareSerial mp3Serial(MP3_RX_PIN, MP3_TX_PIN);
DFRobotDFPlayerMini mp3Player;
// Accelerometer
MPU6050 accelerometer;
// ================================
// SYSTEM STATES
// ================================
enum SystemState {
POWERED_OFF,
SELF_DIAGNOSTIC,
ION_ARM_CHARGING,
WAND_SYNC,
IDLE_OPERATIONAL,
STREAM_CHARGING,
STREAM_ACTIVE,
THERMAL_WARNING,
THERMAL_CRITICAL,
EMERGENCY_VENT,
SAFETY_LOCKOUT,
MALFUNCTION,
CROSSING_STREAMS_WARNING,
CROSSING_STREAMS_CRITICAL
};
SystemState currentState = POWERED_OFF;
SystemState previousState = POWERED_OFF;
// ================================
// ENHANCED PHYSICS ENGINE
// ================================
struct PhysicsEngine {
// Thermal dynamics
float coreTemperature = AMBIENT_TEMP;
float cyclotronTemp = AMBIENT_TEMP;
float wandTemp = AMBIENT_TEMP;
float ambientTemp = AMBIENT_TEMP;
// Power systems
float packVoltage = PACK_VOLTAGE;
float currentDraw = 0.0;
bool powerStable = true;
// Cyclotron mechanics
float currentRPM = 0.0;
float targetRPM = CYCLOTRON_MIN_RPM;
unsigned long cyclotronStartTime = 0;
bool cyclotronStable = false;
float cyclotronHeat = CYCLOTRON_COOL_TEMP;
// Proton stream physics
float streamPower = 0.0;
float streamStability = 100.0;
unsigned long streamStartTime = 0;
unsigned long totalStreamTime = 0;
unsigned long lastStreamEnd = 0;
bool streamOverloaded = false;
// Equipment wear simulation (EEPROM backed)
float equipmentHealth = 100.0;
unsigned long operatingHours = 0;
unsigned long totalShots = 0;
bool requiresMaintenance = false;
// Environmental factors
float humidity = 45.0;
float atmosphericPressure = 1013.25;
bool environmentalWarning = false;
} physics;
// ================================
// RUMBLE HAPTIC FEEDBACK SYSTEM
// ================================
struct RumbleSystem {
bool enabled = true;
uint8_t currentIntensity = 0; // Current PWM value (0-255)
uint8_t targetIntensity = 0; // Target PWM value for smooth transitions
uint8_t baseIntensity = 0; // Base rumble level
// Pattern variables
unsigned long patternStartTime = 0;
unsigned long lastPatternUpdate = 0;
bool patternActive = false;
uint8_t patternType = 0; // 0=off, 1=pulse, 2=ramp, 3=chaos, 4=emergency
// Rumble patterns
float pulsePhase = 0.0; // For sine wave pulsing
uint8_t rampDirection = 1; // For ramping up/down
unsigned long chaosTimer = 0; // For random chaos patterns
// Override system
bool overrideActive = false; // Manual override mode
unsigned long overrideStartTime = 0; // When override started
unsigned long overrideDuration = 200; // How long override lasts (ms)
// Settings
uint8_t maxIntensity = 255; // Maximum rumble strength
float smoothingFactor = 0.7; // Smoothing for transitions (0.0-1.0)
} rumble;
// ================================
// CROSSING STREAMS DETECTION
// ================================
struct CrossingStreams {
bool detectionEnabled = true;
bool streamsDetected = false;
unsigned long crossingStartTime = 0;
float crossingAngle = 0.0;
float crossingDistance = 0.0;
bool warningIssued = false;
bool criticalState = false;
// Accelerometer data
float wandAngleX = 0.0;
float wandAngleY = 0.0;
float wandAngleZ = 0.0;
float lastAngleX = 0.0;
float lastAngleY = 0.0;
float lastAngleZ = 0.0;
// Movement detection
bool wandMoving = false;
float movementThreshold = 5.0; // Degrees per second
} crossing;
// ================================
// SAFETY SYSTEMS
// ================================
struct SafetySystem {
bool interlockEngaged = false;
bool thermalProtection = true;
bool powerProtection = true;
bool radiationShielding = true;
bool emergencyStop = false;
bool diagnosticMode = false;
uint8_t safetyViolations = 0;
bool maintenanceRequired = false;
} safety;
// ================================
// TIMING VARIABLES
// ================================
unsigned long lastPhysicsUpdate = 0;
unsigned long lastVisualUpdate = 0;
unsigned long lastAudioUpdate = 0;
unsigned long lastInputUpdate = 0;
unsigned long lastStatusUpdate = 0;
unsigned long lastSafetyCheck = 0;
unsigned long lastEEPROMSave = 0;
unsigned long lastAccelerometerUpdate = 0;
unsigned long lastRumbleUpdate = 0;
// System timing
unsigned long systemStartTime = 0;
unsigned long stateChangeTime = 0;
// Input states with debouncing
bool ionArmEngaged = false;
bool wandConnected = false;
bool safetyEngaged = false;
bool ventRequested = false;
bool diagnosticRequested = false;
// Input debouncing
unsigned long lastInputChange[6] = {0};
bool lastInputState[6] = {false};
const unsigned long DEBOUNCE_DELAY = 20;
// Debug timing
unsigned long lastDebugOutput = 0;
// Animation variables
uint8_t cyclotronPosition = 0;
int powerCellSequence = 0;
float bargraphLevel = 0.0;
unsigned long lastCyclotronAnimation = 0;
unsigned long lastPowerCellAnimation = 0;
// Audio system - enhanced for MP3
bool audioEnabled = true;
bool mp3Ready = false;
int currentTrack = 0;
bool trackPlaying = false;
unsigned long trackStartTime = 0;
// ================================
// SETUP - ENHANCED INITIALIZATION
// ================================
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("=========================================");
Serial.println(" ENHANCED GHOSTBUSTERS EQUIPMENT v4.0");
Serial.println(" ๐พ EEPROM Storage");
Serial.println(" ๐ฏ Crossing Streams Detection");
Serial.println(" ๐ต Authentic Movie Sounds");
if (TESTING_MODE) {
Serial.println(" *** TESTING MODE ENABLED ***");
}
Serial.println("=========================================");
// Initialize EEPROM
EEPROM.begin(EEPROM_SIZE);
loadDataFromEEPROM();
Serial.println("๐พ EEPROM initialized - persistent data loaded");
// Configure hardware pins
pinMode(ION_ARM_SWITCH, INPUT_PULLUP);
pinMode(WAND_ACTIVATE, INPUT_PULLUP);
pinMode(FIRE_TRIGGER, INPUT_PULLUP);
pinMode(MANUAL_VENT_BTN, INPUT_PULLUP);
pinMode(SAFETY_INTERLOCK, INPUT_PULLUP);
pinMode(PACK_DIAGNOSTIC, INPUT_PULLUP);
pinMode(SMOKE_PIN, OUTPUT);
pinMode(MP3_BUSY_PIN, INPUT);
pinMode(RUMBLE_MOSFET_PIN, OUTPUT);
// Initialize PWM for rumble motor MOSFET control (ESP32 Arduino Core 3.x)
if (ledcAttach(RUMBLE_MOSFET_PIN, RUMBLE_PWM_FREQ, RUMBLE_PWM_RESOLUTION)) {
ledcWrite(RUMBLE_MOSFET_PIN, 0); // Start with rumble off
Serial.println("๐ฎ Rumble motor MOSFET initialized (Pin " + String(RUMBLE_MOSFET_PIN) + ")");
} else {
Serial.println("โ ๏ธ Failed to initialize rumble motor PWM");
}
// Initialize I2C for accelerometer
Wire.begin(SDA_PIN, SCL_PIN);
// Initialize accelerometer
accelerometer.initialize();
if (accelerometer.testConnection()) {
Serial.println("๐ฏ MPU6050 accelerometer initialized");
// Configure accelerometer settings
accelerometer.setFullScaleAccelRange(MPU6050_ACCEL_FS_4);
accelerometer.setDLPFMode(MPU6050_DLPF_BW_20); // Use BW_20 instead of BW_21
crossing.detectionEnabled = true;
} else {
Serial.println("โ ๏ธ MPU6050 not found - crossing streams detection disabled");
crossing.detectionEnabled = false;
}
// Initialize MP3 player
mp3Serial.begin(9600);
delay(500);
if (mp3Player.begin(mp3Serial)) {
Serial.println("๐ต DFPlayer Mini initialized");
mp3Player.volume(20); // Volume 0-30
mp3Player.EQ(DFPLAYER_EQ_NORMAL);
mp3Ready = true;
delay(500);
} else {
Serial.println("โ ๏ธ DFPlayer Mini not found - using fallback audio");
mp3Ready = false;
}
// Initialize LED systems
powerCell.begin();
cyclotron.begin();
wandTip.begin();
wandSide.begin();
wandBargraph.begin();
wandStatus.begin();
// Optimized brightness levels
powerCell.setBrightness(200);
cyclotron.setBrightness(180);
wandTip.setBrightness(255);
wandSide.setBrightness(160);
wandBargraph.setBrightness(200);
wandStatus.setBrightness(180);
// Clear all displays
clearAllSystems();
// Initialize physics engine
initializePhysicsEngine();
// Initialize input states to current pin readings (IMPORTANT!)
for (int i = 0; i < 6; i++) {
// Read current pin states to initialize properly
bool currentPinState = false;
switch (i) {
case 0: currentPinState = !digitalRead(ION_ARM_SWITCH); break;
case 1: currentPinState = !digitalRead(WAND_ACTIVATE); break;
case 2: currentPinState = !digitalRead(SAFETY_INTERLOCK); break;
case 3: currentPinState = !digitalRead(FIRE_TRIGGER); break;
case 4: currentPinState = !digitalRead(MANUAL_VENT_BTN); break;
case 5: currentPinState = !digitalRead(PACK_DIAGNOSTIC); break;
}
lastInputState[i] = currentPinState;
lastInputChange[i] = 0;
// Initialize global state variables based on current pin readings
switch (i) {
case 0: ionArmEngaged = currentPinState; break;
case 1: wandConnected = currentPinState; break;
case 2:
safetyEngaged = currentPinState;
safety.interlockEngaged = !currentPinState;
break;
case 3: /* Fire trigger - don't set state at startup */ break;
case 4: ventRequested = currentPinState; break;
case 5: diagnosticRequested = currentPinState; break;
}
}
// Check if system should start immediately based on current pin states
if (ionArmEngaged && safetyEngaged && currentState == POWERED_OFF) {
Serial.println("๐ฅ INPUTS ALREADY ACTIVE - STARTING DIAGNOSTIC SEQUENCE!");
setState(SELF_DIAGNOSTIC);
}
Serial.println();
Serial.println("๐ง INPUT STATES INITIALIZED:");
Serial.print("Ion Arm: "); Serial.println(ionArmEngaged ? "ENGAGED" : "disengaged");
Serial.print("Safety: "); Serial.println(safetyEngaged ? "READY" : "LOCKED");
Serial.print("Wand: "); Serial.println(wandConnected ? "CONNECTED" : "disconnected");
Serial.println();
Serial.println("๐ง ENHANCED SYSTEM INITIALIZED");
Serial.print("๐พ Equipment Health: "); Serial.print(physics.equipmentHealth, 1); Serial.println("%");
Serial.print("โฐ Operating Hours: "); Serial.print(physics.operatingHours / 3600); Serial.println(" hours");
Serial.print("๐ซ Total Shots Fired: "); Serial.println(physics.totalShots);
Serial.print("๐ฎ Rumble Motor: "); Serial.println(rumble.enabled ? "ENABLED" : "DISABLED");
// Initial pin state check
Serial.println("=== INITIAL PIN STATES ===");
Serial.print("Ion Arm (Pin 16): ");
Serial.print(digitalRead(ION_ARM_SWITCH));
Serial.print(" -> ");
Serial.println(!digitalRead(ION_ARM_SWITCH) ? "ACTIVE" : "inactive");
Serial.print("Safety (Pin 5): ");
Serial.print(digitalRead(SAFETY_INTERLOCK));
Serial.print(" -> ");
Serial.println(!digitalRead(SAFETY_INTERLOCK) ? "DISENGAGED" : "engaged");
Serial.println("Ready for ion arm activation...");
Serial.println();
Serial.println("๐ก CROSSING STREAMS TESTING:");
if (crossing.detectionEnabled) {
Serial.println(" ๐ฏ Accelerometer active - automatic detection enabled");
Serial.println(" ๐ Tilt wand while firing to trigger crossing detection");
} else {
Serial.println(" ๐งช Manual trigger: Hold Fire + Diagnostic together while firing");
}
Serial.println();
Serial.println("๐ฎ RUMBLE MOTOR WIRING (ESP32 Core 3.x):");
Serial.println(" Gate โ Pin 12 (PWM Direct)");
Serial.println(" Drain โ Motor (-) & Ground");
Serial.println(" Source โ Motor (+) & VCC");
Serial.println(" ๐ก Use N-Channel MOSFET (e.g., IRLB8721)");
Serial.println("=========================================");
}
// ================================
// MAIN LOOP - ENHANCED
// ================================
void loop() {
unsigned long currentTime = millis();
// PRIORITY 1: Handle inputs immediately
if (currentTime - lastInputUpdate >= INPUT_UPDATE_INTERVAL) {
handleOptimizedInputs();
lastInputUpdate = currentTime;
}
// PRIORITY 2: Update accelerometer data (if available)
if (crossing.detectionEnabled && currentTime - lastAccelerometerUpdate >= ACCELEROMETER_UPDATE_INTERVAL) {
updateAccelerometerData();
checkCrossingStreams();
lastAccelerometerUpdate = currentTime;
}
// PRIORITY 3: Update physics simulation
if (currentTime - lastPhysicsUpdate >= PHYSICS_UPDATE_INTERVAL) {
updateOptimizedPhysics();
lastPhysicsUpdate = currentTime;
}
// PRIORITY 4: Update visuals at 30fps
if (currentTime - lastVisualUpdate >= VISUAL_UPDATE_INTERVAL) {
updateOptimizedVisuals();
lastVisualUpdate = currentTime;
}
// PRIORITY 5: Update rumble haptics
if (currentTime - lastRumbleUpdate >= RUMBLE_UPDATE_INTERVAL) {
updateRumbleSystem();
lastRumbleUpdate = currentTime;
}
// PRIORITY 6: Update audio system
if (currentTime - lastAudioUpdate >= AUDIO_UPDATE_INTERVAL) {
updateMP3Audio();
lastAudioUpdate = currentTime;
}
// PRIORITY 7: Safety checks
if (currentTime - lastSafetyCheck >= SAFETY_CHECK_INTERVAL) {
updateOptimizedSafety();
lastSafetyCheck = currentTime;
}
// PRIORITY 8: Save to EEPROM periodically
if (currentTime - lastEEPROMSave >= EEPROM_SAVE_INTERVAL) {
saveDataToEEPROM();
lastEEPROMSave = currentTime;
}
// PRIORITY 9: Status reporting
if (currentTime - lastStatusUpdate >= STATUS_UPDATE_INTERVAL) {
reportOptimizedStatus();
lastStatusUpdate = currentTime;
}
// Update system state machine
updateOptimizedStateMachine();
}
// ================================
// EEPROM PERSISTENCE SYSTEM
// ================================
void loadDataFromEEPROM() {
// Check if EEPROM has valid data
byte checksum = EEPROM.read(ADDR_CHECKSUM);
byte calculatedChecksum = calculateEEPROMChecksum();
if (checksum == calculatedChecksum && checksum != 0xFF) {
// Load stored data
EEPROM.get(ADDR_EQUIPMENT_HEALTH, physics.equipmentHealth);
EEPROM.get(ADDR_OPERATING_HOURS, physics.operatingHours);
EEPROM.get(ADDR_TOTAL_SHOTS, physics.totalShots);
physics.requiresMaintenance = EEPROM.read(ADDR_MAINTENANCE_FLAG);
Serial.println("๐พ Restored equipment data from EEPROM");
} else {
// Initialize with defaults
physics.equipmentHealth = 100.0;
physics.operatingHours = 0;
physics.totalShots = 0;
physics.requiresMaintenance = false;
Serial.println("๐พ EEPROM empty - initialized with defaults");
saveDataToEEPROM(); // Save defaults
}
}
void saveDataToEEPROM() {
EEPROM.put(ADDR_EQUIPMENT_HEALTH, physics.equipmentHealth);
EEPROM.put(ADDR_OPERATING_HOURS, physics.operatingHours);
EEPROM.put(ADDR_TOTAL_SHOTS, physics.totalShots);
EEPROM.write(ADDR_MAINTENANCE_FLAG, physics.requiresMaintenance);
unsigned long currentTime = millis();
EEPROM.put(ADDR_LAST_SAVE_TIME, currentTime);
byte checksum = calculateEEPROMChecksum();
EEPROM.write(ADDR_CHECKSUM, checksum);
EEPROM.commit();
static unsigned long lastSaveMessage = 0;
if (millis() - lastSaveMessage > 60000) { // Message every minute max
Serial.println("๐พ Equipment data saved to EEPROM");
lastSaveMessage = millis();
}
}
byte calculateEEPROMChecksum() {
byte checksum = 0;
for (int i = 0; i < ADDR_CHECKSUM; i++) {
checksum ^= EEPROM.read(i);
}
return checksum;
}
// ================================
// ACCELEROMETER & CROSSING STREAMS
// ================================
void updateAccelerometerData() {
// Skip if accelerometer not available
if (!crossing.detectionEnabled) return;
int16_t ax, ay, az;
int16_t gx, gy, gz;
// Read accelerometer data with error checking
accelerometer.getAcceleration(&ax, &ay, &az);
accelerometer.getRotation(&gx, &gy, &gz);
// Basic sanity check - if all values are 0, sensor might have failed
if (ax == 0 && ay == 0 && az == 0) {
static unsigned long lastErrorReport = 0;
if (millis() - lastErrorReport > 10000) { // Report error every 10 seconds max
Serial.println("โ ๏ธ Accelerometer read error - crossing detection may be unreliable");
lastErrorReport = millis();
}
return;
}
// Convert to g-force (8192 = 1g for ยฑ4g range)
float accelX = ax / 8192.0;
float accelY = ay / 8192.0;
float accelZ = az / 8192.0;
// Store previous angles
crossing.lastAngleX = crossing.wandAngleX;
crossing.lastAngleY = crossing.wandAngleY;
crossing.lastAngleZ = crossing.wandAngleZ;
// Calculate wand orientation angles
crossing.wandAngleX = atan2(accelY, accelZ) * 180.0 / PI;
crossing.wandAngleY = atan2(accelX, accelZ) * 180.0 / PI;
crossing.wandAngleZ = atan2(accelX, accelY) * 180.0 / PI;
// Detect if wand is moving
float angleChangeX = abs(crossing.wandAngleX - crossing.lastAngleX);
float angleChangeY = abs(crossing.wandAngleY - crossing.lastAngleY);
float angleChangeZ = abs(crossing.wandAngleZ - crossing.lastAngleZ);
float totalMovement = angleChangeX + angleChangeY + angleChangeZ;
crossing.wandMoving = (totalMovement > crossing.movementThreshold);
}
void checkCrossingStreams() {
if (!crossing.detectionEnabled || currentState != STREAM_ACTIVE) {
crossing.streamsDetected = false;
crossing.warningIssued = false;
crossing.criticalState = false;
return;
}
// SIMPLIFIED CROSSING DETECTION:
// Check if wand is pointed at specific angle (simulating another stream)
// In real implementation, this would use multiple accelerometers or IR beams
bool potentialCrossing = false;
// METHOD 1: Accelerometer-based detection
if (crossing.detectionEnabled) {
// Detect if wand is held at "crossing" angle (45-135 degrees from vertical)
if (abs(crossing.wandAngleX) > 30.0 && abs(crossing.wandAngleX) < 150.0) {
if (abs(crossing.wandAngleY) > 30.0 && abs(crossing.wandAngleY) < 150.0) {
potentialCrossing = true;
}
}
// Additional check: rapid movement while firing (simulating tracking another stream)
if (crossing.wandMoving && currentState == STREAM_ACTIVE) {
potentialCrossing = true;
}
}
// METHOD 2: Manual trigger for testing (hold Fire + Diagnostic buttons together)
// This provides a way to test crossing streams without accelerometer
if (!digitalRead(FIRE_TRIGGER) && !digitalRead(PACK_DIAGNOSTIC)) {
potentialCrossing = true;
Serial.println("๐งช MANUAL CROSSING STREAMS TRIGGER ACTIVATED");
}
if (potentialCrossing && !crossing.streamsDetected) {
// Start crossing detection
crossing.streamsDetected = true;
crossing.crossingStartTime = millis();
crossing.crossingAngle = abs(crossing.wandAngleX) + abs(crossing.wandAngleY);
Serial.println("โ ๏ธ POTENTIAL STREAM CROSSING DETECTED!");
if (crossing.detectionEnabled) {
Serial.print("Wand angles: X=");
Serial.print(crossing.wandAngleX, 1);
Serial.print("ยฐ, Y=");
Serial.print(crossing.wandAngleY, 1);
Serial.println("ยฐ");
}
} else if (crossing.streamsDetected && potentialCrossing) {
// Continue crossing detection
unsigned long crossingDuration = millis() - crossing.crossingStartTime;
if (crossingDuration > 2000 && !crossing.warningIssued) {
// Issue warning after 2 seconds
crossing.warningIssued = true;
setState(CROSSING_STREAMS_WARNING);
Serial.println("๐จ DON'T CROSS THE STREAMS!");
} else if (crossingDuration > 5000 && !crossing.criticalState) {
// Critical state after 5 seconds
crossing.criticalState = true;
setState(CROSSING_STREAMS_CRITICAL);
Serial.println("๐จ๐ฅ CRITICAL CROSSING STREAMS EVENT!");
}
} else if (crossing.streamsDetected && !potentialCrossing) {
// Crossing ended
crossing.streamsDetected = false;
crossing.warningIssued = false;
crossing.criticalState = false;
if (currentState == CROSSING_STREAMS_WARNING || currentState == CROSSING_STREAMS_CRITICAL) {
setState(STREAM_ACTIVE); // Return to normal firing
}
Serial.println("โ
Stream crossing resolved");
}
}
// ================================
// ENHANCED MP3 AUDIO SYSTEM
// ================================
void updateMP3Audio() {
// Check if current track finished
if (mp3Ready && trackPlaying) {
bool busy = digitalRead(MP3_BUSY_PIN);
if (!busy && millis() - trackStartTime > 1000) { // Track finished (with 1s minimum)
trackPlaying = false;
currentTrack = 0;
}
}
}
void playMP3Track(int trackNumber, bool loop = false) {
if (!mp3Ready) return;
if (currentTrack != trackNumber || !trackPlaying) {
currentTrack = trackNumber;
trackStartTime = millis();
trackPlaying = true;
if (loop) {
mp3Player.loop(trackNumber);
} else {
mp3Player.play(trackNumber);
}
Serial.print("๐ต Playing track ");
Serial.print(trackNumber);
Serial.print(": ");
Serial.println(getTrackName(trackNumber));
}
}
void stopMP3() {
if (mp3Ready && trackPlaying) {
mp3Player.stop();
trackPlaying = false;
currentTrack = 0;
}
}
String getTrackName(int track) {
switch (track) {
case TRACK_STARTUP: return "Startup";
case TRACK_ION_ARM_CHARGE: return "Ion Arm Charging";
case TRACK_WAND_SYNC: return "Wand Sync";
case TRACK_READY: return "System Ready";
case TRACK_STREAM_CHARGE: return "Stream Charging";
case TRACK_STREAM_FIRE: return "Proton Stream";
case TRACK_STREAM_LOOP: return "Firing Loop";
case TRACK_VENT_MANUAL: return "Manual Vent";
case TRACK_VENT_EMERGENCY: return "Emergency Vent";
case TRACK_CROSSING_WARNING: return "Crossing Warning";
case TRACK_CROSSING_EXPLOSION: return "Crossing Explosion";
case TRACK_SHUTDOWN: return "Shutdown";
case TRACK_MALFUNCTION: return "Malfunction";
case TRACK_LOW_BATTERY: return "Low Battery";
default: return "Unknown";
}
}
// ================================
// PWM HELPER FUNCTION (ESP32 Core 3.x)
// ================================
void writeRumblePWM(uint8_t value) {
ledcWrite(RUMBLE_MOSFET_PIN, value); // ESP32 Core 3.x uses pin directly
}
// ================================
// ENHANCED RUMBLE HAPTIC SYSTEM (ESP32 Core 3.x)
// ================================
void updateRumbleSystem() {
if (!rumble.enabled) {
writeRumblePWM(0);
return;
}
// Check if override timeout has expired
if (rumble.overrideActive && millis() - rumble.overrideStartTime > rumble.overrideDuration) {
rumble.overrideActive = false;
}
// If not in override mode, calculate normal rumble
if (!rumble.overrideActive) {
// Calculate base rumble intensity based on system state
calculateRumbleIntensity();
// Apply rumble patterns
applyRumblePatterns();
// Smooth transitions for better feel
smoothRumbleTransitions();
}
// Apply final intensity to MOSFET
writeRumblePWM(rumble.currentIntensity);
}
void calculateRumbleIntensity() {
uint8_t newIntensity = 0;
switch (currentState) {
case POWERED_OFF:
newIntensity = 0;
rumble.patternType = 0; // No pattern
break;
case SELF_DIAGNOSTIC:
newIntensity = 30;
rumble.patternType = 1; // Gentle pulse
break;
case ION_ARM_CHARGING:
newIntensity = 40 + (physics.packVoltage / PACK_VOLTAGE * 30); // 40-70 range
rumble.patternType = 2; // Ramp up
break;
case WAND_SYNC:
newIntensity = 60;
rumble.patternType = 1; // Sync pulse
break;
case IDLE_OPERATIONAL:
// Base rumble proportional to cyclotron RPM
newIntensity = 20 + (physics.currentRPM / CYCLOTRON_MAX_RPM * 40); // 20-60 range
rumble.patternType = 0; // Steady
// Add cyclotron heat effects
if (physics.cyclotronHeat > CYCLOTRON_WARM_TEMP) {
newIntensity += (physics.cyclotronHeat - CYCLOTRON_WARM_TEMP) / 2; // Extra rumble when hot
}
break;
case STREAM_CHARGING:
// Ramping intensity as stream charges
newIntensity = 80 + (physics.streamPower * 0.6); // 80-140 range
rumble.patternType = 2; // Charging ramp
break;
case STREAM_ACTIVE:
// High intensity proportional to stream power and stability
newIntensity = 120 + (physics.streamPower * 0.8); // 120-200 range
// Reduce intensity if stream is unstable (vibration gets erratic)
if (physics.streamStability < 80.0) {
rumble.patternType = 3; // Chaos pattern
} else {
rumble.patternType = 0; // Steady high rumble
}
// CROSSING STREAMS EFFECTS
if (crossing.streamsDetected) {
newIntensity = 255; // MAXIMUM RUMBLE!
rumble.patternType = 3; // Chaotic rumble
}
break;
case CROSSING_STREAMS_WARNING:
newIntensity = 200;
rumble.patternType = 4; // Emergency warning pattern
break;
case CROSSING_STREAMS_CRITICAL:
newIntensity = 255; // MAXIMUM POWER!
rumble.patternType = 3; // Wild chaos rumble
break;
case EMERGENCY_VENT:
newIntensity = 180;
rumble.patternType = 4; // Emergency pattern
break;
case THERMAL_WARNING:
case THERMAL_CRITICAL:
newIntensity = 100 + (physics.coreTemperature - MAX_SAFE_TEMP) * 3;
rumble.patternType = 4; // Warning pattern
break;
case MALFUNCTION:
newIntensity = 150;
rumble.patternType = 3; // Malfunction chaos
break;
default:
newIntensity = 0;
rumble.patternType = 0;
break;
}
// Equipment health affects rumble (worn equipment vibrates more)
if (physics.equipmentHealth < 90.0) {
float healthFactor = 1.0 + ((100.0 - physics.equipmentHealth) / 100.0) * 0.3; // Up to 30% more rumble
newIntensity = (uint8_t)(newIntensity * healthFactor);
}
// Low battery causes weak rumble
if (physics.packVoltage < MIN_VOLTAGE + 1.0) {
newIntensity = (uint8_t)(newIntensity * 0.6); // 40% reduction
}
// Constrain to maximum
rumble.baseIntensity = constrain(newIntensity, 0, rumble.maxIntensity);
}
void applyRumblePatterns() {
unsigned long now = millis();
uint8_t patternIntensity = rumble.baseIntensity;
switch (rumble.patternType) {
case 0: // Steady - no pattern
rumble.targetIntensity = patternIntensity;
break;
case 1: // Pulse pattern - sine wave
{
if (!rumble.patternActive) {
rumble.patternActive = true;
rumble.patternStartTime = now;
rumble.pulsePhase = 0.0;
}
// 2Hz pulse frequency (500ms period)
rumble.pulsePhase += (now - rumble.lastPatternUpdate) / 250.0; // 250ms = half period
float pulseFactor = (sin(rumble.pulsePhase) + 1.0) / 2.0; // 0.0 to 1.0
rumble.targetIntensity = (uint8_t)(patternIntensity * (0.3 + pulseFactor * 0.7)); // 30-100% range
}
break;
case 2: // Ramp pattern - smooth up/down
{
if (!rumble.patternActive) {
rumble.patternActive = true;
rumble.patternStartTime = now;
rumble.rampDirection = 1; // Start ramping up
}
// Ramp cycle every 1 second
unsigned long rampTime = (now - rumble.patternStartTime) % 1000;
float rampFactor;
if (rampTime < 500) {
rampFactor = rampTime / 500.0; // Ramp up 0.0 to 1.0
} else {
rampFactor = (1000 - rampTime) / 500.0; // Ramp down 1.0 to 0.0
}
rumble.targetIntensity = (uint8_t)(patternIntensity * (0.2 + rampFactor * 0.8)); // 20-100% range
}
break;
case 3: // Chaos pattern - random for crossing streams/malfunction
{
if (!rumble.patternActive) {
rumble.patternActive = true;
rumble.patternStartTime = now;
rumble.chaosTimer = now;
}
// Change intensity every 50-200ms randomly
if (now - rumble.chaosTimer > (50 + random(150))) {
// Random intensity between 50-100% of base
float chaosFactor = 0.5 + (random(100) / 100.0) * 0.5;
rumble.targetIntensity = (uint8_t)(patternIntensity * chaosFactor);
rumble.chaosTimer = now;
}
}
break;
case 4: // Emergency pattern - rapid pulse
{
if (!rumble.patternActive) {
rumble.patternActive = true;
rumble.patternStartTime = now;
}
// 5Hz emergency pulse (100ms on, 100ms off)
unsigned long pulseTime = (now - rumble.patternStartTime) % 200;
if (pulseTime < 100) {
rumble.targetIntensity = patternIntensity; // Full intensity
} else {
rumble.targetIntensity = patternIntensity / 3; // 33% intensity
}
}
break;
default:
rumble.targetIntensity = 0;
break;
}
rumble.lastPatternUpdate = now;
}
void smoothRumbleTransitions() {
// Smooth transition to target intensity
float difference = rumble.targetIntensity - rumble.currentIntensity;
if (abs(difference) > 1) {
// Apply smoothing factor (higher = more responsive, lower = smoother)
rumble.currentIntensity += (int)(difference * rumble.smoothingFactor);
} else {
rumble.currentIntensity = rumble.targetIntensity;
}
// Ensure bounds
rumble.currentIntensity = constrain(rumble.currentIntensity, 0, rumble.maxIntensity);
}
void setRumbleIntensity(uint8_t intensity, bool override = false) {
if (override) {
rumble.overrideActive = true;
rumble.overrideStartTime = millis();
rumble.currentIntensity = constrain(intensity, 0, rumble.maxIntensity);
writeRumblePWM(rumble.currentIntensity);
} else {
rumble.overrideActive = false;
}
}
void stopRumble() {
rumble.enabled = false;
rumble.currentIntensity = 0;
rumble.targetIntensity = 0;
rumble.patternActive = false;
writeRumblePWM(0);
}
void enableRumble(bool enable) {
rumble.enabled = enable;
if (!enable) {
stopRumble();
}
Serial.print("๐ฎ Rumble motor: ");
Serial.println(enable ? "ENABLED" : "DISABLED");
}
// ================================
// PHYSICS ENGINE - ENHANCED
// ================================
void initializePhysicsEngine() {
physics.coreTemperature = AMBIENT_TEMP;
physics.cyclotronTemp = AMBIENT_TEMP;
physics.wandTemp = AMBIENT_TEMP;
physics.cyclotronHeat = CYCLOTRON_COOL_TEMP;
physics.packVoltage = PACK_VOLTAGE;
physics.currentRPM = 0.0;
physics.streamPower = 0.0;
physics.streamStability = 100.0;
Serial.println("โก Enhanced physics engine initialized");
Serial.print("๐พ Equipment health loaded: ");
Serial.print(physics.equipmentHealth, 1);
Serial.println("%");
}
void updateOptimizedPhysics() {
float deltaTime = PHYSICS_UPDATE_INTERVAL / 1000.0;
// Update operating hours when system is active
if (currentState >= IDLE_OPERATIONAL) {
physics.operatingHours++;
}
// Optimized thermal dynamics
updateThermalDynamicsOptimized(deltaTime);
// Optimized power systems
updatePowerSystemsOptimized();
// Optimized cyclotron physics
updateCyclotronPhysicsOptimized();
// Optimized proton stream physics
updateProtonStreamPhysicsOptimized();
// Equipment wear tracking
updateEquipmentWear();
}
void updateThermalDynamicsOptimized(float deltaTime) {
// Fast heat generation calculation
float heatGeneration = 0.0;
switch (currentState) {
case ION_ARM_CHARGING:
case WAND_SYNC:
heatGeneration = 0.5;
break;
case IDLE_OPERATIONAL:
heatGeneration = 0.8;
break;
case STREAM_CHARGING:
heatGeneration = 1.5;
break;
case STREAM_ACTIVE:
heatGeneration = FIRING_HEAT_RATE;
// Crossing streams generates MASSIVE heat
if (crossing.streamsDetected) {
heatGeneration *= 3.0; // Triple heat when crossing streams!
}
if (millis() - physics.streamStartTime > 5000) {
heatGeneration *= 1.5;
}
break;
case EMERGENCY_VENT:
heatGeneration = -VENT_COOLING_RATE;
break;
case CROSSING_STREAMS_CRITICAL:
heatGeneration = FIRING_HEAT_RATE * 5.0; // Extreme heat!
break;
default:
heatGeneration = -0.2;
break;
}
// Manual vent override
if (ventRequested) {
heatGeneration = -VENT_COOLING_RATE * 0.7;
}
// Apply thermal dynamics
float tempDelta = (heatGeneration - HEAT_DISSIPATION * (physics.coreTemperature - physics.ambientTemp)) * deltaTime;
physics.coreTemperature += tempDelta * THERMAL_MASS;
physics.coreTemperature = constrain(physics.coreTemperature, physics.ambientTemp, 200.0); // Higher max for crossing streams
// Cyclotron and wand temperatures
physics.cyclotronTemp += (physics.coreTemperature - physics.cyclotronTemp) * 0.5 * deltaTime;
physics.wandTemp += (physics.coreTemperature - physics.wandTemp) * 0.6 * deltaTime;
// Cyclotron heat
updateCyclotronHeatOptimized(deltaTime);
}
void updateCyclotronHeatOptimized(float deltaTime) {
float heatChange = 0.0;
// Heat from RPM
if (physics.currentRPM > CYCLOTRON_MIN_RPM) {
float rpmRatio = physics.currentRPM / CYCLOTRON_MAX_RPM;
heatChange += rpmRatio * rpmRatio * CYCLOTRON_RPM_HEAT_FACTOR * 50;
}
// Heat during firing
if (currentState == STREAM_ACTIVE) {
heatChange += CYCLOTRON_HEAT_RATE;
// MASSIVE heat during crossing streams
if (crossing.streamsDetected) {
heatChange += CYCLOTRON_HEAT_RATE * 4.0; // 5x total heat!
Serial.println("๐ฅ CYCLOTRON OVERHEATING - CROSSING STREAMS!");
}
unsigned long firingTime = millis() - physics.streamStartTime;
if (firingTime > 3000) {
heatChange += 1.0;
}
}
// Heat during charging
if (currentState == STREAM_CHARGING) {
heatChange += CYCLOTRON_HEAT_RATE * 0.3;
}
// Critical heat during crossing streams critical state
if (currentState == CROSSING_STREAMS_CRITICAL) {
heatChange += CYCLOTRON_HEAT_RATE * 6.0; // Extreme overheating!
}
// Natural cooling
if (currentState <= IDLE_OPERATIONAL || currentState == EMERGENCY_VENT) {
float coolingRate = CYCLOTRON_COOL_RATE;
if (currentState == EMERGENCY_VENT || ventRequested) {
coolingRate *= 3.0;
}
heatChange -= coolingRate * (physics.cyclotronHeat - CYCLOTRON_COOL_TEMP) * 0.1;
}
// Apply heat change
physics.cyclotronHeat += heatChange * deltaTime * 10;
physics.cyclotronHeat = constrain(physics.cyclotronHeat, CYCLOTRON_COOL_TEMP, 150.0); // Higher max for emergencies
}
void updatePowerSystemsOptimized() {
// Power calculation with crossing streams drain
float powerDraw = 0.0;
switch (currentState) {
case SELF_DIAGNOSTIC: powerDraw = 0.5; break;
case ION_ARM_CHARGING: powerDraw = 2.1; break;
case WAND_SYNC: powerDraw = 1.8; break;
case IDLE_OPERATIONAL: powerDraw = 3.2; break;
case STREAM_CHARGING: powerDraw = 8.5; break;
case STREAM_ACTIVE:
powerDraw = 15.7 + (physics.streamPower * 0.1);
if (crossing.streamsDetected) {
powerDraw *= 2.5; // Crossing streams drains power rapidly!
}
break;
case EMERGENCY_VENT: powerDraw = 6.8; break;
case CROSSING_STREAMS_WARNING: powerDraw = 20.0; break;
case CROSSING_STREAMS_CRITICAL: powerDraw = 35.0; break; // Massive power draw!
default: powerDraw = 0.1; break;
}
// Apply voltage drop
physics.packVoltage -= powerDraw * VOLTAGE_DROP_RATE;
if (powerDraw < 1.0) {
physics.packVoltage += CHARGING_RECOVERY;
}
physics.packVoltage = constrain(physics.packVoltage, 10.0, PACK_VOLTAGE);
physics.powerStable = (physics.packVoltage > MIN_VOLTAGE);
physics.currentDraw = powerDraw;
// Low battery warning
static bool lowBatteryWarned = false;
if (physics.packVoltage < MIN_VOLTAGE + 1.0 && !lowBatteryWarned) {
playMP3Track(TRACK_LOW_BATTERY);
lowBatteryWarned = true;
Serial.println("๐ LOW BATTERY WARNING");
} else if (physics.packVoltage > MIN_VOLTAGE + 2.0) {
lowBatteryWarned = false;
}
}
void updateCyclotronPhysicsOptimized() {
if (currentState >= ION_ARM_CHARGING && physics.powerStable) {
unsigned long elapsed = millis() - physics.cyclotronStartTime;
// Faster acceleration - 2 seconds to full speed
float progressRatio = (float)elapsed / 2000.0;
progressRatio = constrain(progressRatio, 0.0, 1.0);
float easedProgress = progressRatio * progressRatio;
physics.currentRPM = CYCLOTRON_MIN_RPM + (physics.targetRPM - CYCLOTRON_MIN_RPM) * easedProgress;
// Stability check
physics.cyclotronStable = (physics.currentRPM > 200);
// Temperature affects performance
if (physics.cyclotronTemp > MAX_SAFE_TEMP) {
physics.currentRPM *= 0.9;
physics.cyclotronStable = (physics.currentRPM > 150);
}
// Crossing streams destabilizes cyclotron
if (crossing.streamsDetected) {
physics.currentRPM *= 0.7; // Reduce RPM when crossing streams
physics.cyclotronStable = false;
}
} else {
// Spin-down
physics.currentRPM *= 0.95;
if (physics.currentRPM < 10.0) physics.currentRPM = 0.0;
physics.cyclotronStable = false;
}
}
void updateProtonStreamPhysicsOptimized() {
if (currentState == STREAM_CHARGING || currentState == STREAM_ACTIVE) {
unsigned long streamTime = millis() - physics.streamStartTime;
// Stream power buildup
float chargeProgress = (float)streamTime / (float)STREAM_CHARGE_TIME;
chargeProgress = constrain(chargeProgress, 0.0, 1.0);
physics.streamPower = chargeProgress * 100.0;
// Instability calculation
float instabilityFactor = 1.0;
if (physics.coreTemperature > MAX_SAFE_TEMP) {
instabilityFactor += 0.5;
}
if (streamTime > 8000) {
instabilityFactor += 0.3;
}
// Crossing streams creates massive instability
if (crossing.streamsDetected) {
instabilityFactor += 2.0; // Major instability!
Serial.println("โก STREAM INSTABILITY - CROSSING DETECTED!");
}
physics.streamStability = 100.0 / instabilityFactor;
physics.streamStability = constrain(physics.streamStability, 10.0, 100.0); // Lower minimum for crossing
// Check for stream overload
physics.streamOverloaded = (streamTime > STREAM_MAX_DURATION || physics.streamStability < 20.0);
// Crossing streams critical overload
if (crossing.criticalState) {
physics.streamOverloaded = true;
}
} else {
// Stream decay
physics.streamPower *= 0.9;
physics.streamStability += (100.0 - physics.streamStability) * 0.2;
physics.streamOverloaded = false;
}
}
void updateEquipmentWear() {
// Equipment degradation tracking
// Wear from high temperature
if (physics.coreTemperature > MAX_SAFE_TEMP) {
physics.equipmentHealth -= 0.001;
}
// Wear from proton stream usage
if (currentState == STREAM_ACTIVE) {
physics.equipmentHealth -= 0.002;
// MASSIVE wear from crossing streams
if (crossing.streamsDetected) {
physics.equipmentHealth -= 0.01; // 5x wear rate!
}
}
// Extreme wear during crossing critical state
if (currentState == CROSSING_STREAMS_CRITICAL) {
physics.equipmentHealth -= 0.05; // Severe equipment damage!
}
// Natural degradation
physics.equipmentHealth -= 0.00001;
// Constrain health
physics.equipmentHealth = constrain(physics.equipmentHealth, 0.0, 100.0);
// Maintenance requirements
static unsigned long lastMaintenanceCheck = 0;
if (millis() - lastMaintenanceCheck > 300000) { // Check every 5 minutes
if (physics.equipmentHealth < 80.0 || physics.operatingHours > 36000) {
physics.requiresMaintenance = true;
}
lastMaintenanceCheck = millis();
}
}
// ================================
// INPUT HANDLING - ENHANCED
// ================================
void handleOptimizedInputs() {
// Read all inputs with debouncing
bool inputs[6] = {
!digitalRead(ION_ARM_SWITCH),
!digitalRead(WAND_ACTIVATE),
!digitalRead(SAFETY_INTERLOCK),
!digitalRead(FIRE_TRIGGER),
!digitalRead(MANUAL_VENT_BTN),
!digitalRead(PACK_DIAGNOSTIC)
};
unsigned long now = millis();
// DEBUG: Show input states when system is off
if (currentState == POWERED_OFF && now - lastDebugOutput > 2000) {
Serial.println("=== INPUT DEBUG ===");
Serial.print("Ion Arm (Pin 16): Raw=");
Serial.print(digitalRead(ION_ARM_SWITCH));
Serial.print(" Processed=");
Serial.print(inputs[0] ? "ACTIVE" : "inactive");
Serial.print(" State=");
Serial.println(ionArmEngaged ? "ENGAGED" : "disengaged");
Serial.print("Safety (Pin 5): Raw=");
Serial.print(digitalRead(SAFETY_INTERLOCK));
Serial.print(" Processed=");
Serial.print(inputs[2] ? "DISENGAGED" : "engaged");
Serial.print(" State=");
Serial.println(safetyEngaged ? "READY" : "LOCKED");
if (ionArmEngaged && safetyEngaged) {
Serial.println(">>> CONDITIONS MET - Should start diagnostic!");
}
Serial.println("==================");
lastDebugOutput = now;
}
// Process each input with debouncing
for (int i = 0; i < 6; i++) {
if (inputs[i] != lastInputState[i]) {
lastInputChange[i] = now;
}
if ((now - lastInputChange[i]) > DEBOUNCE_DELAY) {
if (inputs[i] != lastInputState[i]) {
lastInputState[i] = inputs[i];
handleInputChange(i, inputs[i]);
}
}
}
}
void handleInputChange(int inputIndex, bool state) {
switch (inputIndex) {
case 0: // Ion Arm Switch
ionArmEngaged = state;
Serial.print("โก Ion Arm Switch: ");
Serial.println(state ? "ACTIVATED" : "DEACTIVATED");
if (state && safetyEngaged && currentState == POWERED_OFF) {
Serial.println(">>> โก STARTING DIAGNOSTIC SEQUENCE!");
setState(SELF_DIAGNOSTIC);
} else if (!state && currentState > POWERED_OFF) {
Serial.println(">>> โก Ion arm disengaged - powering down");
setState(POWERED_OFF);
}
break;
case 1: // Wand Activate
wandConnected = state;
Serial.print("๐ซ Wand: ");
Serial.println(state ? "CONNECTED" : "DISCONNECTED");
if (state && currentState == ION_ARM_CHARGING) {
setState(WAND_SYNC);
} else if (!state && currentState >= WAND_SYNC) {
setState(ION_ARM_CHARGING);
}
break;
case 2: // Safety Interlock
safetyEngaged = state;
safety.interlockEngaged = !state;
Serial.print("๐ก๏ธ Safety interlock: ");
Serial.println(state ? "DISENGAGED (Ready)" : "ENGAGED (Locked)");
if (!state && currentState > POWERED_OFF) {
setState(POWERED_OFF);
} else if (state && ionArmEngaged && currentState == POWERED_OFF) {
setState(SELF_DIAGNOSTIC);
}
break;
case 3: // Fire Trigger
if (state) {
Serial.println("๐ฅ Fire Trigger: PRESSED");
if (currentState == IDLE_OPERATIONAL || (TESTING_MODE && currentState >= ION_ARM_CHARGING)) {
if (physics.cyclotronHeat <= CYCLOTRON_HOT_TEMP || TESTING_MODE) {
if (physics.currentRPM > 0 || TESTING_MODE) {
Serial.println(" >>> ๐ฅ FIRING AUTHORIZED!");
physics.streamStartTime = millis();
physics.totalShots++; // Increment shot counter for EEPROM
setState(STREAM_CHARGING);
}
}
}
} else {
Serial.println("๐ฅ Fire Trigger: RELEASED");
if (currentState == STREAM_CHARGING || currentState == STREAM_ACTIVE ||
currentState == CROSSING_STREAMS_WARNING || currentState == CROSSING_STREAMS_CRITICAL) {
physics.lastStreamEnd = millis();
setState(IDLE_OPERATIONAL);
}
}
break;
case 4: // Manual Vent
ventRequested = state;
Serial.print("๐จ Manual Vent: ");
Serial.println(state ? "ACTIVATED" : "DEACTIVATED");
if (state) {
digitalWrite(SMOKE_PIN, HIGH);
playMP3Track(TRACK_VENT_MANUAL);
} else if (currentState != EMERGENCY_VENT) {
digitalWrite(SMOKE_PIN, LOW);
}
break;
case 5: // Diagnostic
diagnosticRequested = state;
if (state && currentState >= IDLE_OPERATIONAL) {
runQuickDiagnostic();
}
break;
}
}
// ================================
// STATE MACHINE - ENHANCED
// ================================
void setState(SystemState newState) {
if (newState == currentState) return;
previousState = currentState;
currentState = newState;
stateChangeTime = millis();
Serial.print("๐ State change: ");
Serial.print(getStateName(previousState));
Serial.print(" โ ");
Serial.println(getStateName(currentState));
handleStateEntryOptimized(newState);
}
void updateOptimizedStateMachine() {
unsigned long elapsed = millis() - stateChangeTime;
switch (currentState) {
case SELF_DIAGNOSTIC:
if (elapsed >= 500) {
setState(ION_ARM_CHARGING);
}
break;
case ION_ARM_CHARGING:
if (elapsed >= 1500) {
physics.cyclotronStartTime = millis();
physics.targetRPM = CYCLOTRON_MAX_RPM;
if (wandConnected) {
setState(WAND_SYNC);
} else if (elapsed >= 5000) {
setState(IDLE_OPERATIONAL);
}
}
break;
case WAND_SYNC:
if (elapsed >= 1000) {
setState(IDLE_OPERATIONAL);
}
break;
case STREAM_CHARGING:
if (elapsed >= STREAM_CHARGE_TIME) {
setState(STREAM_ACTIVE);
}
break;
case STREAM_ACTIVE:
// Check for overload conditions
if (physics.streamOverloaded || physics.coreTemperature > EMERGENCY_TEMP) {
setState(EMERGENCY_VENT);
}
// Auto-vent when cyclotron gets blazing hot
if (physics.cyclotronHeat > CYCLOTRON_BLAZING_TEMP) {
setState(EMERGENCY_VENT);
}
break;
case CROSSING_STREAMS_WARNING:
// Continue normal firing but with warning
if (!crossing.streamsDetected) {
setState(STREAM_ACTIVE);
} else if (crossing.criticalState) {
setState(CROSSING_STREAMS_CRITICAL);
}
break;
case CROSSING_STREAMS_CRITICAL:
// EMERGENCY! Force shutdown after 3 seconds
if (elapsed >= 3000) {
Serial.println("๐ฅ CROSSING STREAMS EXPLOSION SEQUENCE!");
setState(EMERGENCY_VENT);
// Major equipment damage
physics.equipmentHealth -= 10.0;
physics.coreTemperature = EMERGENCY_TEMP;
}
break;
case EMERGENCY_VENT:
if (elapsed >= 8000) {
digitalWrite(SMOKE_PIN, LOW);
}
if (elapsed >= 3000) {
bool thermalRecovery = (physics.coreTemperature < MAX_SAFE_TEMP);
bool cyclotronCooled = (physics.cyclotronHeat < CYCLOTRON_WARM_TEMP);
if (thermalRecovery && cyclotronCooled) {
setState(IDLE_OPERATIONAL);
digitalWrite(SMOKE_PIN, LOW);
} else if (elapsed >= 10000) {
setState(IDLE_OPERATIONAL);
}
}
break;
}
}
void handleStateEntryOptimized(SystemState state) {
// Reset rumble pattern when state changes
rumble.patternActive = false;
rumble.patternStartTime = millis();
switch (state) {
case POWERED_OFF:
clearAllSystems();
digitalWrite(SMOKE_PIN, LOW);
stopMP3();
stopRumble(); // Turn off rumble completely
physics.currentRPM = 0.0;
physics.streamPower = 0.0;
crossing.streamsDetected = false;
crossing.warningIssued = false;
crossing.criticalState = false;
playMP3Track(TRACK_SHUTDOWN);
break;
case SELF_DIAGNOSTIC:
enableRumble(true); // Enable rumble for startup
playMP3Track(TRACK_STARTUP);
break;
case ION_ARM_CHARGING:
playMP3Track(TRACK_ION_ARM_CHARGE);
break;
case WAND_SYNC:
playMP3Track(TRACK_WAND_SYNC);
break;
case IDLE_OPERATIONAL:
playMP3Track(TRACK_READY);
break;
case STREAM_CHARGING:
playMP3Track(TRACK_STREAM_CHARGE);
// Extra rumble burst when starting to charge (non-blocking)
setRumbleIntensity(150, true);
// Reset override after next rumble update cycle
break;
case STREAM_ACTIVE:
playMP3Track(TRACK_STREAM_FIRE);
break;
case CROSSING_STREAMS_WARNING:
playMP3Track(TRACK_CROSSING_WARNING);
break;
case CROSSING_STREAMS_CRITICAL:
playMP3Track(TRACK_CROSSING_EXPLOSION);
// MASSIVE rumble burst for crossing streams (non-blocking)
setRumbleIntensity(255, true);
// Reset override after next rumble update cycle
break;
case EMERGENCY_VENT:
digitalWrite(SMOKE_PIN, HIGH);
playMP3Track(TRACK_VENT_EMERGENCY);
break;
case MALFUNCTION:
playMP3Track(TRACK_MALFUNCTION);
break;
}
}
// ================================
// VISUAL EFFECTS - ENHANCED
// ================================
void updateOptimizedVisuals() {
updatePowerCellOptimized();
updateCyclotronOptimized();
updateWandVisualsOptimized();
}
void updatePowerCellOptimized() {
powerCell.clear();
if (currentState >= ION_ARM_CHARGING) {
float voltageRatio = physics.packVoltage / PACK_VOLTAGE;
int activeLEDs = (int)(voltageRatio * POWER_CELL_LEDS);
// Color based on voltage and state
uint32_t baseColor;
if (currentState == CROSSING_STREAMS_CRITICAL) {
baseColor = powerCell.Color(255, 0, 255); // Magenta for critical crossing
} else if (currentState == CROSSING_STREAMS_WARNING) {
baseColor = powerCell.Color(255, 100, 0); // Orange for crossing warning
} else if (voltageRatio > 0.85) {
baseColor = powerCell.Color(0, 0, 255); // Blue - normal
} else if (voltageRatio > 0.7) {
baseColor = powerCell.Color(0, 255, 255); // Cyan - warning
} else if (voltageRatio > 0.5) {
baseColor = powerCell.Color(255, 255, 0); // Yellow - low
} else {
baseColor = powerCell.Color(255, 0, 0); // Red - critical
}
// Animation speed based on current draw
if (millis() - lastPowerCellAnimation > (200 - (int)(physics.currentDraw * 5))) {
powerCellSequence = (powerCellSequence + 1) % POWER_CELL_LEDS;
lastPowerCellAnimation = millis();
}
// Draw power cell with crossing streams effects
for (int i = 0; i < activeLEDs; i++) {
uint8_t brightness = (i == powerCellSequence) ? 255 : 120;
// Flickering during crossing streams
if (crossing.streamsDetected && random(100) < 30) {
brightness /= 2;
}
uint8_t r = ((baseColor >> 16) & 0xFF) * brightness / 255;
uint8_t g = ((baseColor >> 8) & 0xFF) * brightness / 255;
uint8_t b = (baseColor & 0xFF) * brightness / 255;
powerCell.setPixelColor(i, powerCell.Color(r, g, b));
}
}
powerCell.show();
}
void updateCyclotronOptimized() {
cyclotron.clear();
if (currentState >= ION_ARM_CHARGING && physics.currentRPM > 0) {
// Animation speed based on RPM
int animationDelay = 100 - (physics.currentRPM / 50);
if (crossing.streamsDetected) animationDelay /= 2; // Faster when crossing
if (millis() - lastCyclotronAnimation > animationDelay) {
cyclotronPosition = (cyclotronPosition + 1) % CYCLOTRON_LEDS;
lastCyclotronAnimation = millis();
}
// Heat color with crossing streams enhancement
uint32_t cyclotronColor = getCyclotronHeatColorOptimized(physics.cyclotronHeat);
// Special effects for crossing streams
if (currentState == CROSSING_STREAMS_CRITICAL) {
cyclotronColor = cyclotron.Color(255, 255, 255); // Blazing white
} else if (currentState == CROSSING_STREAMS_WARNING) {
cyclotronColor = cyclotron.Color(255, 0, 255); // Magenta warning
}
// More segments when crossing streams or overheated
int numSegments = 4;
if (crossing.streamsDetected) numSegments = 8; // More activity
else if (physics.cyclotronHeat > CYCLOTRON_WARM_TEMP) numSegments = 6;
else if (physics.cyclotronHeat > CYCLOTRON_HOT_TEMP) numSegments = 8;
// Draw rotating segments
for (int seg = 0; seg < numSegments; seg++) {
int segmentPos = (cyclotronPosition + (seg * CYCLOTRON_LEDS / numSegments)) % CYCLOTRON_LEDS;
cyclotron.setPixelColor(segmentPos, cyclotronColor);
// Enhanced trailing effect
int trailLength = crossing.streamsDetected ? 5 : 3;
for (int t = 1; t <= trailLength; t++) {
int trailPos = (segmentPos - t + CYCLOTRON_LEDS) % CYCLOTRON_LEDS;
uint8_t trailBrightness = 255 - (t * 40);
uint8_t r = ((cyclotronColor >> 16) & 0xFF) * trailBrightness / 255;
uint8_t g = ((cyclotronColor >> 8) & 0xFF) * trailBrightness / 255;
uint8_t b = (cyclotronColor & 0xFF) * trailBrightness / 255;
cyclotron.setPixelColor(trailPos, cyclotron.Color(r, g, b));
}
}
// Wild flickering during critical crossing
if (currentState == CROSSING_STREAMS_CRITICAL) {
for (int i = 0; i < CYCLOTRON_LEDS; i++) {
if (random(100) < 50) {
cyclotron.setPixelColor(i, cyclotron.Color(255, 255, 255));
}
}
}
}
cyclotron.show();
}
uint32_t getCyclotronHeatColorOptimized(float temperature) {
if (temperature <= CYCLOTRON_COOL_TEMP) {
return cyclotron.Color(255, 0, 0); // Red
} else if (temperature <= CYCLOTRON_WARM_TEMP) {
return cyclotron.Color(255, 100, 0); // Orange
} else if (temperature <= CYCLOTRON_HOT_TEMP) {
return cyclotron.Color(255, 200, 0); // Yellow
} else if (temperature <= CYCLOTRON_BLAZING_TEMP) {
return cyclotron.Color(255, 255, 100); // White-yellow
} else {
return cyclotron.Color(255, 255, 255); // White
}
}
void updateWandVisualsOptimized() {
// Enhanced wand tip with crossing streams effects
wandTip.clear();
if (currentState == STREAM_CHARGING || currentState == STREAM_ACTIVE ||
currentState == CROSSING_STREAMS_WARNING || currentState == CROSSING_STREAMS_CRITICAL) {
uint32_t coreColor, crackleColor;
if (currentState == CROSSING_STREAMS_CRITICAL) {
// CRITICAL CROSSING - Chaotic white/magenta energy
coreColor = wandTip.Color(255, 255, 255); // Blazing white core
crackleColor = wandTip.Color(255, 0, 255); // Magenta crackling
} else if (currentState == CROSSING_STREAMS_WARNING) {
// WARNING - Orange core with white crackling
coreColor = wandTip.Color(255, 100, 0); // Warning orange
crackleColor = wandTip.Color(255, 255, 255); // White warning crackling
} else if (currentState == STREAM_CHARGING) {
// CHARGING - Building orange glow
uint8_t intensity = (uint8_t)(physics.streamPower * 2.55);
coreColor = wandTip.Color(intensity, intensity/2, 0);
crackleColor = wandTip.Color(intensity/3, intensity/3, intensity/2);
} else {
// NORMAL FIRING - Classic Ghostbusters orange
coreColor = wandTip.Color(255, 140, 0);
crackleColor = wandTip.Color(200, 200, 255);
}
// Enhanced crackling for crossing streams
static bool crackleState[WAND_TIP_LEDS];
static unsigned long lastCrackleUpdate = 0;
int crackleSpeed = crossing.streamsDetected ? 25 : 75; // Faster crackling when crossing
int crackleChance = crossing.streamsDetected ? 70 : 40; // More crackling when crossing
if (millis() - lastCrackleUpdate > crackleSpeed) {
for (int i = 0; i < WAND_TIP_LEDS; i++) {
crackleState[i] = (random(100) < crackleChance);
}
lastCrackleUpdate = millis();
}
// Draw wand tip
for (int i = 0; i < WAND_TIP_LEDS; i++) {
if (i < 2) {
wandTip.setPixelColor(i, coreColor); // Core LEDs
} else {
wandTip.setPixelColor(i, crackleState[i] ? crackleColor : 0); // Crackling
}
}
// Chaos mode for critical crossing
if (currentState == CROSSING_STREAMS_CRITICAL) {
for (int i = 0; i < WAND_TIP_LEDS; i++) {
if (random(100) < 80) {
uint32_t chaosColor = wandTip.Color(random(255), random(255), random(255));
wandTip.setPixelColor(i, chaosColor);
}
}
}
}
wandTip.show();
// Quick thermal strip, bargraph, and status updates
// (Keeping these simple for performance)
wandSide.clear();
if (currentState >= IDLE_OPERATIONAL) {
float tempRatio = (physics.wandTemp - AMBIENT_TEMP) / (CRITICAL_TEMP - AMBIENT_TEMP);
tempRatio = constrain(tempRatio, 0.0, 1.0);
int heatLEDs = (int)(tempRatio * WAND_SIDE_LEDS);
for (int i = 0; i < heatLEDs; i++) {
if (crossing.streamsDetected) {
wandSide.setPixelColor(i, wandSide.Color(255, 0, 255)); // Magenta when crossing
} else if (i < WAND_SIDE_LEDS / 2) {
wandSide.setPixelColor(i, wandSide.Color(0, 255, 0)); // Green
} else {
wandSide.setPixelColor(i, wandSide.Color(255, 0, 0)); // Red
}
}
}
wandSide.show();
// Bargraph shows stream power or crossing intensity
wandBargraph.clear();
if (currentState >= IDLE_OPERATIONAL) {
float level = physics.streamPower / 100.0;
if (crossing.streamsDetected) {
level = 1.0; // Full bar when crossing streams
}
int powerLEDs = (int)(level * WAND_BARGRAPH_LEDS);
for (int i = 0; i < powerLEDs; i++) {
uint32_t color = crossing.streamsDetected ?
wandBargraph.Color(255, 0, 255) : // Magenta for crossing
wandBargraph.Color(0, 255, 0); // Green for normal
wandBargraph.setPixelColor(i, color);
}
}
wandBargraph.show();
// Status LED
wandStatus.clear();
if (currentState >= IDLE_OPERATIONAL) {
uint32_t statusColor;
switch (currentState) {
case IDLE_OPERATIONAL: statusColor = wandStatus.Color(0, 255, 0); break;
case STREAM_CHARGING: statusColor = wandStatus.Color(255, 255, 0); break;
case STREAM_ACTIVE: statusColor = wandStatus.Color(255, 0, 0); break;
case CROSSING_STREAMS_WARNING: statusColor = wandStatus.Color(255, 100, 0); break;
case CROSSING_STREAMS_CRITICAL: statusColor = wandStatus.Color(255, 0, 255); break;
default: statusColor = wandStatus.Color(100, 100, 100); break;
}
wandStatus.setPixelColor(0, statusColor);
}
wandStatus.show();
}
// ================================
// SAFETY SYSTEM - ENHANCED
// ================================
void updateOptimizedSafety() {
if (TESTING_MODE) {
// Minimal safety in testing mode
if (physics.coreTemperature > 180.0) { // Very high threshold
setState(EMERGENCY_VENT);
}
return;
}
// Enhanced safety checks
if (physics.coreTemperature > EMERGENCY_TEMP) {
setState(EMERGENCY_VENT);
}
if (physics.packVoltage < MIN_VOLTAGE && currentState > POWERED_OFF) {
setState(POWERED_OFF);
}
if (physics.streamOverloaded) {
setState(EMERGENCY_VENT);
}
// Crossing streams safety override
if (crossing.criticalState && currentState != CROSSING_STREAMS_CRITICAL) {
setState(CROSSING_STREAMS_CRITICAL);
}
// Equipment health critical
if (physics.equipmentHealth < 10.0) {
setState(MALFUNCTION);
}
}
// ================================
// STATUS REPORTING - ENHANCED
// ================================
void reportOptimizedStatus() {
if (currentState == POWERED_OFF) return;
Serial.print("๐ ");
Serial.print(getStateName(currentState));
Serial.print(" | Core: ");
Serial.print(physics.coreTemperature, 0);
Serial.print("ยฐC | Cyclotron: ");
Serial.print(physics.cyclotronHeat, 0);
Serial.print("ยฐC | V: ");
Serial.print(physics.packVoltage, 1);
Serial.print("V | Health: ");
Serial.print(physics.equipmentHealth, 0);
Serial.print("% | Rumble: ");
Serial.print(rumble.currentIntensity);
if (crossing.streamsDetected) {
Serial.print(" | ๐ฏCROSSING!");
}
if (TESTING_MODE) Serial.print(" | ๐งชTEST");
Serial.println();
}
// ================================
// DIAGNOSTIC SYSTEM - ENHANCED
// ================================
void runQuickDiagnostic() {
Serial.println("๐ง ENHANCED DIAGNOSTIC:");
Serial.print("๐พ Equipment Health: "); Serial.print(physics.equipmentHealth, 1); Serial.println("%");
Serial.print("โฐ Operating Hours: "); Serial.print(physics.operatingHours / 3600); Serial.println(" hrs");
Serial.print("๐ซ Total Shots: "); Serial.println(physics.totalShots);
Serial.print("๐ก๏ธ Core Temp: "); Serial.print(physics.coreTemperature, 1); Serial.println("ยฐC");
Serial.print("๐ Cyclotron Heat: "); Serial.print(physics.cyclotronHeat, 1); Serial.println("ยฐC");
Serial.print("๐ Voltage: "); Serial.print(physics.packVoltage, 1); Serial.println("V");
Serial.print("โก RPM: "); Serial.print(physics.currentRPM, 0); Serial.println();
Serial.print("๐ฎ Rumble: "); Serial.print(rumble.enabled ? "ON" : "OFF");
Serial.print(" ("); Serial.print(rumble.currentIntensity); Serial.print("/255, Pattern: ");
Serial.print(rumble.patternType); Serial.println(")");
if (crossing.detectionEnabled) {
Serial.print("๐ฏ Crossing Detection: ENABLED");
if (crossing.streamsDetected) {
Serial.print(" | โ ๏ธ STREAMS DETECTED!");
}
Serial.println();
}
if (physics.requiresMaintenance) {
Serial.println("๐ง โ ๏ธ MAINTENANCE REQUIRED");
} else {
Serial.println("โ
All systems nominal");
}
}
// ================================
// UTILITY FUNCTIONS
// ================================
void clearAllSystems() {
powerCell.clear(); powerCell.show();
cyclotron.clear(); cyclotron.show();
wandTip.clear(); wandTip.show();
wandSide.clear(); wandSide.show();
wandBargraph.clear(); wandBargraph.show();
wandStatus.clear(); wandStatus.show();
}
String getStateName(SystemState state) {
switch (state) {
case POWERED_OFF: return "OFF";
case SELF_DIAGNOSTIC: return "DIAG";
case ION_ARM_CHARGING: return "CHARGING";
case WAND_SYNC: return "SYNC";
case IDLE_OPERATIONAL: return "READY";
case STREAM_CHARGING: return "CHARGE";
case STREAM_ACTIVE: return "FIRING";
case THERMAL_WARNING: return "WARN";
case THERMAL_CRITICAL: return "CRITICAL";
case EMERGENCY_VENT: return "VENT";
case SAFETY_LOCKOUT: return "LOCKOUT";
case MALFUNCTION: return "FAULT";
case CROSSING_STREAMS_WARNING: return "CROSS-WARN";
case CROSSING_STREAMS_CRITICAL: return "CROSS-CRIT";
default: return "UNKNOWN";
}
}