#include "behaviors.h" #include // ============================================================================ // Base Behavior Implementation // ============================================================================ Behavior::Behavior() { controlledMotors.clear(); } void Behavior::addMotor(uint8_t motorID) { // Check if motor already in list for (uint8_t id : controlledMotors) { if (id == motorID) { return; // Already added } } controlledMotors.push_back(motorID); } void Behavior::removeMotor(uint8_t motorID) { controlledMotors.erase( std::remove(controlledMotors.begin(), controlledMotors.end(), motorID), controlledMotors.end() ); } void Behavior::clearMotors() { controlledMotors.clear(); } // ============================================================================ // Focus Behavior Implementation // ============================================================================ FocusBehavior::FocusBehavior() { isActive = false; eyePosition = settings.eyeCenter; neckPosition = settings.neckCenter; neckNormalized = 0.0f; faceDetectedTime = 0; faceWasPresent = false; // Add motors to controlled list addMotor(settings.eyeMotor1); addMotor(settings.eyeMotor2); addMotor(settings.neckMotor); } bool FocusBehavior::update() { uint8_t faceCount = faceDetect.getFaceCount(); unsigned long now = millis(); // ---- No face detected ---- if (faceCount == 0 || !faceDetect.getFace(0).valid) { isActive = false; faceWasPresent = false; // Smoothly return eyes and neck to center eyePosition = lerp(eyePosition, settings.eyeCenter, settings.eyeCenteringSpeed); neckNormalized = lerpf(neckNormalized, 0.0f, settings.neckCenteringSpeed); neckPosition = normalizedToServo( settings.neckInvert ? -neckNormalized : neckNormalized, settings.neckCenter, settings.neckMin, settings.neckMax ); return false; } // ---- Face detected ---- isActive = true; const DetectedFace& face = faceDetect.getFace(0); // Track when we first saw a face (for neck delay) if (!faceWasPresent) { faceDetectedTime = now; faceWasPresent = true; } // Normalize face x to -1..+1 float faceNorm = (float)face.x / settings.faceXMax; if (faceNorm < -1.0f) faceNorm = -1.0f; if (faceNorm > 1.0f) faceNorm = 1.0f; // ---- Neck: smoothly follow the target after a delay ---- float neckTarget = faceNorm * settings.neckContribution; if (now - faceDetectedTime >= settings.neckDelayMs) { // Neck is allowed to move - smoothly interpolate toward target neckNormalized = lerpf(neckNormalized, neckTarget, settings.neckSpeed); } // else: neck stays where it is during the delay period // Convert neck normalized position to servo units neckPosition = normalizedToServo( settings.neckInvert ? -neckNormalized : neckNormalized, settings.neckCenter, settings.neckMin, settings.neckMax ); // ---- Eyes: dart to the remainder that the neck hasn't covered ---- // The eyes compensate for whatever offset the neck hasn't reached yet // As the neck catches up, this remainder shrinks toward 0 (eyes center) float eyeNorm = faceNorm - neckNormalized; // Clamp eye normalized to -1..+1 if (eyeNorm < -1.0f) eyeNorm = -1.0f; if (eyeNorm > 1.0f) eyeNorm = 1.0f; // Convert to servo position and interpolate quickly uint16_t eyeTarget = normalizedToServo(eyeNorm, settings.eyeCenter, settings.eyeMin, settings.eyeMax); eyePosition = lerp(eyePosition, eyeTarget, settings.eyeSpeed); return true; } bool FocusBehavior::getMotorPosition(uint8_t motorID, uint16_t& position) { if (motorID == settings.eyeMotor1 || motorID == settings.eyeMotor2) { position = eyePosition; return true; } if (motorID == settings.neckMotor) { position = neckPosition; return true; } return false; } uint16_t FocusBehavior::normalizedToServo(float n, uint16_t center, uint16_t min, uint16_t max) const { // Map a normalized value (-1..+1) to servo range, handling asymmetric ranges float rangeNeg = (float)(center - min); float rangePos = (float)(max - center); float posFloat; if (n < 0.0f) { posFloat = (float)center + (n * rangeNeg); } else { posFloat = (float)center + (n * rangePos); } int16_t pos = (int16_t)posFloat; if (pos < (int16_t)min) pos = (int16_t)min; if (pos > (int16_t)max) pos = (int16_t)max; return (uint16_t)pos; } float FocusBehavior::lerpf(float current, float target, float t) { float diff = target - current; if (fabs(diff) < 0.001f) return target; return current + diff * t; } uint16_t FocusBehavior::lerp(uint16_t current, uint16_t target, float t) { int16_t diff = (int16_t)target - (int16_t)current; if (abs(diff) < 2) return target; return (uint16_t)((int16_t)current + (int16_t)(diff * t)); } // ============================================================================ // Idle Behavior Implementation // ============================================================================ IdleBehavior::IdleBehavior() { startTime = millis(); // Initialize all motor positions to center for (int i = 0; i < 256; i++) { motorPositions[i] = POSITION_CENTER; } } void IdleBehavior::initMotors(const std::vector& motorIDs) { clearMotors(); for (uint8_t id : motorIDs) { addMotor(id); motorPositions[id] = POSITION_CENTER; } } bool IdleBehavior::update() { unsigned long now = millis(); float timeOffset = (float)(now - startTime) * NOISE_SPEED; // Update position for each controlled motor using perlin noise for (uint8_t motorID : controlledMotors) { // Use motor ID as seed offset for variety between motors uint16_t seed = motorID * MOTOR_SEED_OFFSET; // Get perlin noise value (-1 to 1 range approximately) float noiseValue = perlin1D_octave(seed, timeOffset, 3, 0.5f); // Map noise to position range: center ± NOISE_RANGE // Perlin noise typically returns values in roughly -1 to 1 range int16_t offset = (int16_t)(noiseValue * (float)NOISE_RANGE); // Calculate final position int16_t position = (int16_t)POSITION_CENTER + offset; // Clamp to valid servo range if (position < 1547) position = 1547; // center - 500 if (position > 2547) position = 2547; // center + 500 motorPositions[motorID] = (uint16_t)position; } // Idle behavior is always active (but low priority) return true; } bool IdleBehavior::getMotorPosition(uint8_t motorID, uint16_t& position) { // Check if we control this motor for (uint8_t id : controlledMotors) { if (id == motorID) { position = motorPositions[motorID]; return true; } } return false; } // ============================================================================ // Viseme Behavior Implementation // ============================================================================ VisemeBehavior::VisemeBehavior() { isActive = false; lastTriggerTime = 0; nextVisemeID = 0; currentPositions.clear(); visemes.clear(); } Viseme* VisemeBehavior::findViseme(uint8_t id) { for (Viseme& v : visemes) { if (v.id == id) { return &v; } } return nullptr; } uint8_t VisemeBehavior::addViseme(const char* label) { Viseme newViseme; newViseme.id = nextVisemeID++; // Copy label (3 chars, ensure null-terminated) if (label && strlen(label) >= 3) { newViseme.label[0] = label[0]; newViseme.label[1] = label[1]; newViseme.label[2] = label[2]; newViseme.label[3] = '\0'; } else { // Default label if not provided or too short newViseme.label[0] = 'V'; newViseme.label[1] = 'I'; newViseme.label[2] = 'S'; newViseme.label[3] = '\0'; } newViseme.motorPositions.clear(); visemes.push_back(newViseme); Serial.print("[Viseme] Added viseme '"); Serial.print(newViseme.label); Serial.print("' with ID "); Serial.println(newViseme.id); return newViseme.id; } void VisemeBehavior::addViseme(uint8_t id, uint16_t pos40, uint16_t pos43, uint16_t pos44) { // Legacy method for backwards compatibility Viseme* existing = findViseme(id); if (existing) { // Update existing viseme existing->motorPositions.clear(); existing->motorPositions.push_back({40, pos40}); existing->motorPositions.push_back({43, pos43}); existing->motorPositions.push_back({44, pos44}); } else { // Add new viseme Viseme newViseme; newViseme.id = id; // Default label based on ID (V + 2 digit ID) newViseme.label[0] = 'V'; if (id < 10) { newViseme.label[1] = '0' + id; newViseme.label[2] = ' '; } else if (id < 100) { newViseme.label[1] = '0' + (id / 10); newViseme.label[2] = '0' + (id % 10); } else { newViseme.label[1] = 'X'; newViseme.label[2] = 'X'; } newViseme.label[3] = '\0'; newViseme.motorPositions.push_back({40, pos40}); newViseme.motorPositions.push_back({43, pos43}); newViseme.motorPositions.push_back({44, pos44}); visemes.push_back(newViseme); // Update nextVisemeID if needed if (id >= nextVisemeID) { nextVisemeID = id + 1; } } // Update controlled motors list addMotor(40); addMotor(43); addMotor(44); } // Overload to add viseme with explicit label void VisemeBehavior::addViseme(uint8_t id, const char* label, uint16_t pos40, uint16_t pos43, uint16_t pos44) { Viseme* existing = findViseme(id); if (existing) { // Update existing viseme if (label) { existing->label[0] = label[0]; existing->label[1] = label[1]; existing->label[2] = label[2]; existing->label[3] = '\0'; } existing->motorPositions.clear(); existing->motorPositions.push_back({40, pos40}); existing->motorPositions.push_back({43, pos43}); existing->motorPositions.push_back({44, pos44}); } else { // Add new viseme Viseme newViseme; newViseme.id = id; // Set label if (label) { newViseme.label[0] = label[0]; newViseme.label[1] = label[1]; newViseme.label[2] = label[2]; newViseme.label[3] = '\0'; } else { // Default label newViseme.label[0] = 'V'; if (id < 10) { newViseme.label[1] = '0' + id; newViseme.label[2] = ' '; } else if (id < 100) { newViseme.label[1] = '0' + (id / 10); newViseme.label[2] = '0' + (id % 10); } else { newViseme.label[1] = 'X'; newViseme.label[2] = 'X'; } newViseme.label[3] = '\0'; } newViseme.motorPositions.push_back({40, pos40}); newViseme.motorPositions.push_back({43, pos43}); newViseme.motorPositions.push_back({44, pos44}); visemes.push_back(newViseme); // Update nextVisemeID if needed if (id >= nextVisemeID) { nextVisemeID = id + 1; } } // Update controlled motors list addMotor(40); addMotor(43); addMotor(44); } bool VisemeBehavior::deleteViseme(uint8_t visemeID) { for (auto it = visemes.begin(); it != visemes.end(); ++it) { if (it->id == visemeID) { Serial.print("[Viseme] Deleted viseme ID "); Serial.println(visemeID); visemes.erase(it); return true; } } Serial.print("[Viseme] Delete failed - unknown viseme ID "); Serial.println(visemeID); return false; } bool VisemeBehavior::setVisemeMotors(uint8_t visemeID, const std::vector& positions) { Viseme* viseme = findViseme(visemeID); if (!viseme) { Serial.print("[Viseme] setVisemeMotors failed - unknown viseme ID "); Serial.println(visemeID); return false; } // Update motor positions viseme->motorPositions = positions; // Update controlled motors list for (const auto& pos : positions) { addMotor(pos.motorID); } Serial.print("[Viseme] Updated viseme ID "); Serial.print(visemeID); Serial.print(" with "); Serial.print(positions.size()); Serial.println(" motors"); return true; } bool VisemeBehavior::setVisemeMotorsAndLabel(uint8_t visemeID, const char* label, const std::vector& positions) { Viseme* viseme = findViseme(visemeID); if (!viseme) { Serial.print("[Viseme] setVisemeMotorsAndLabel failed - unknown viseme ID "); Serial.println(visemeID); return false; } // Update label (3 bytes) if (label) { viseme->label[0] = label[0]; viseme->label[1] = label[1]; viseme->label[2] = label[2]; viseme->label[3] = '\0'; } // Update motor positions viseme->motorPositions = positions; // Update controlled motors list for (const auto& pos : positions) { addMotor(pos.motorID); } Serial.print("[Viseme] Updated viseme ID "); Serial.print(visemeID); Serial.print(" label '"); Serial.print(viseme->label); Serial.print("' with "); Serial.print(positions.size()); Serial.println(" motors"); return true; } bool VisemeBehavior::createOrUpdateViseme(uint8_t visemeID, const char* label, const std::vector& positions) { Viseme* viseme = findViseme(visemeID); if (viseme) { // Update existing return setVisemeMotorsAndLabel(visemeID, label, positions); } else { // Create new Viseme newViseme; newViseme.id = visemeID; // Set label if (label) { newViseme.label[0] = label[0]; newViseme.label[1] = label[1]; newViseme.label[2] = label[2]; newViseme.label[3] = '\0'; } else { // Default label newViseme.label[0] = 'V'; if (visemeID < 10) { newViseme.label[1] = '0' + visemeID; newViseme.label[2] = ' '; } else if (visemeID < 100) { newViseme.label[1] = '0' + (visemeID / 10); newViseme.label[2] = '0' + (visemeID % 10); } else { newViseme.label[1] = 'X'; newViseme.label[2] = 'X'; } newViseme.label[3] = '\0'; } // Set motor positions newViseme.motorPositions = positions; visemes.push_back(newViseme); // Update controlled motors list for (const auto& pos : positions) { addMotor(pos.motorID); } // Update nextVisemeID if needed if (visemeID >= nextVisemeID) { nextVisemeID = visemeID + 1; } Serial.print("[Viseme] Created viseme ID "); Serial.print(visemeID); Serial.print(" label '"); Serial.print(newViseme.label); Serial.print("' with "); Serial.print(positions.size()); Serial.println(" motors"); return true; } } bool VisemeBehavior::triggerViseme(uint8_t visemeID) { Viseme* viseme = findViseme(visemeID); if (!viseme) { Serial.print("[Viseme] Unknown viseme ID "); Serial.println(visemeID); return false; } // Copy positions for this viseme currentPositions = viseme->motorPositions; // Activate and reset timer isActive = true; lastTriggerTime = millis(); Serial.print("[Viseme] Triggered '"); Serial.print(viseme->label); Serial.print("' (ID "); Serial.print(visemeID); Serial.println(")"); return true; } bool VisemeBehavior::update() { if (!isActive) { return false; } // Check for timeout unsigned long now = millis(); if (now - lastTriggerTime >= TIMEOUT_MS) { // Timeout reached - deactivate isActive = false; currentPositions.clear(); Serial.println("[Viseme] Timeout - deactivated"); return false; } return true; } bool VisemeBehavior::getMotorPosition(uint8_t motorID, uint16_t& position) { if (!isActive) { return false; } // Look up motor in current positions for (const auto& pos : currentPositions) { if (pos.motorID == motorID) { position = pos.position; return true; } } return false; } // ============================================================================ // Behavior Manager Implementation // ============================================================================ BehaviorManager behaviorManager; VisemeBehavior visemeBehavior; BehaviorManager::BehaviorManager() { behaviors.clear(); // Initialize all enabled states to false for (int i = 0; i < 256; i++) { enabledStates[i] = false; } } void BehaviorManager::addBehavior(BehaviorID id, Behavior* behavior) { if (behavior == nullptr) return; // Check if already added for (const auto& entry : behaviors) { if (entry.behavior == behavior || entry.id == id) return; } behaviors.push_back({id, behavior}); // New behaviors are enabled by default enabledStates[id] = true; } void BehaviorManager::removeBehavior(Behavior* behavior) { behaviors.erase( std::remove_if(behaviors.begin(), behaviors.end(), [behavior](const BehaviorEntry& entry) { return entry.behavior == behavior; }), behaviors.end() ); } void BehaviorManager::setBehaviorEnabled(BehaviorID id, bool enabled) { enabledStates[id] = enabled; } bool BehaviorManager::isBehaviorEnabled(BehaviorID id) const { return enabledStates[id]; } uint8_t BehaviorManager::getBehaviorCount() const { return behaviors.size(); } bool BehaviorManager::getBehaviorInfo(uint8_t index, BehaviorID& id, bool& enabled) const { if (index >= behaviors.size()) { return false; } id = behaviors[index].id; enabled = enabledStates[id]; return true; } void BehaviorManager::update() { // Update all enabled behaviors for (const auto& entry : behaviors) { if (entry.behavior && enabledStates[entry.id]) { entry.behavior->update(); } } } bool BehaviorManager::getMotorPosition(uint8_t motorID, uint16_t& position) { // Check all enabled behaviors to see if any wants to control this motor for (const auto& entry : behaviors) { if (entry.behavior && enabledStates[entry.id] && entry.behavior->getMotorPosition(motorID, position)) { return true; // Found an enabled behavior controlling this motor } } return false; // No enabled behavior controlling this motor }