2026-01-20 16:32:05 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
#include <Arduino.h>
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
#include "sensors.h"
|
|
|
|
|
|
#include "robotconfig.h"
|
2026-01-21 14:43:12 +00:00
|
|
|
|
#include "noise.h"
|
2026-01-20 16:32:05 +00:00
|
|
|
|
|
2026-01-21 06:49:48 +00:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Behavior IDs
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
enum BehaviorID : uint8_t {
|
2026-02-07 15:51:20 +00:00
|
|
|
|
BEHAVIOR_FOCUS = 1, // Focus behavior (face tracking)
|
2026-01-21 14:43:12 +00:00
|
|
|
|
BEHAVIOR_IDLE = 2, // Idle behavior (perlin noise for all motors)
|
2026-01-25 06:44:08 +00:00
|
|
|
|
BEHAVIOR_VISEME = 3, // Viseme behavior (mouth motor positions)
|
2026-01-21 06:49:48 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-20 16:32:05 +00:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Base Behavior Class
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class Behavior {
|
|
|
|
|
|
public:
|
|
|
|
|
|
Behavior();
|
|
|
|
|
|
virtual ~Behavior() = default;
|
|
|
|
|
|
|
|
|
|
|
|
// Get list of motor IDs this behavior controls
|
|
|
|
|
|
const std::vector<uint8_t>& getControlledMotors() const { return controlledMotors; }
|
|
|
|
|
|
|
|
|
|
|
|
// Add a motor to the controlled list
|
|
|
|
|
|
void addMotor(uint8_t motorID);
|
|
|
|
|
|
|
|
|
|
|
|
// Remove a motor from the controlled list
|
|
|
|
|
|
void removeMotor(uint8_t motorID);
|
|
|
|
|
|
|
|
|
|
|
|
// Clear all controlled motors
|
|
|
|
|
|
void clearMotors();
|
|
|
|
|
|
|
|
|
|
|
|
// Virtual method to update the behavior (called each frame)
|
|
|
|
|
|
// Returns true if the behavior is active and wants to control motors
|
|
|
|
|
|
virtual bool update() = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Virtual method to get the desired position for a motor
|
|
|
|
|
|
// Returns true if this behavior wants to control this motor, false otherwise
|
|
|
|
|
|
virtual bool getMotorPosition(uint8_t motorID, uint16_t& position) = 0;
|
|
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
std::vector<uint8_t> controlledMotors;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
2026-02-07 15:51:20 +00:00
|
|
|
|
// Focus Behavior - Tracks faces with eyes/neck via FaceDetect sensor
|
2026-01-20 16:32:05 +00:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
2026-02-07 16:21:32 +00:00
|
|
|
|
// Setting IDs for SSET protocol command
|
|
|
|
|
|
// Float values are transmitted as fixed-point × 1000 (e.g., 0.15 → 150)
|
|
|
|
|
|
// Signed values are transmitted as int16 reinterpreted as uint16
|
|
|
|
|
|
namespace SettingID {
|
|
|
|
|
|
// Focus: Motor IDs
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_MOTOR_1 = 0x0500;
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_MOTOR_2 = 0x0501;
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_MOTOR = 0x0502;
|
|
|
|
|
|
// Focus: Eye servo range
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_CENTER = 0x0503;
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_MIN = 0x0504;
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_MAX = 0x0505;
|
|
|
|
|
|
// Focus: Neck servo range
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_CENTER = 0x0506;
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_MIN = 0x0507;
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_MAX = 0x0508;
|
|
|
|
|
|
// Focus: Face x range (int16, signed)
|
|
|
|
|
|
constexpr uint16_t FOCUS_FACE_X_MIN = 0x0509;
|
|
|
|
|
|
constexpr uint16_t FOCUS_FACE_X_MAX = 0x050A;
|
|
|
|
|
|
// Focus: Interpolation speeds (float × 1000)
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_SPEED = 0x050B;
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_SPEED = 0x050C;
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_RETURN_SPEED = 0x050D;
|
|
|
|
|
|
// Focus: Neck delay (ms, uint16)
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_DELAY_MS = 0x050E;
|
|
|
|
|
|
// Focus: Neck contribution (float × 1000)
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_CONTRIBUTION = 0x050F;
|
|
|
|
|
|
// Focus: Neck invert (0 or 1)
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_INVERT = 0x0510;
|
|
|
|
|
|
// Focus: Centering speeds (float × 1000)
|
|
|
|
|
|
constexpr uint16_t FOCUS_EYE_CENTERING = 0x0511;
|
|
|
|
|
|
constexpr uint16_t FOCUS_NECK_CENTERING = 0x0512;
|
|
|
|
|
|
|
|
|
|
|
|
constexpr uint16_t FOCUS_FIRST = 0x0500;
|
|
|
|
|
|
constexpr uint16_t FOCUS_LAST = 0x0512;
|
|
|
|
|
|
constexpr uint16_t FOCUS_COUNT = FOCUS_LAST - FOCUS_FIRST + 1;
|
2026-02-09 15:47:08 +00:00
|
|
|
|
|
|
|
|
|
|
// WiFi / WebSocket settings (0x0600 range)
|
|
|
|
|
|
constexpr uint16_t WIFI_SSID = 0x0600; // string (max 32 chars)
|
|
|
|
|
|
constexpr uint16_t WIFI_PASSWORD = 0x0601; // string (max 64 chars)
|
|
|
|
|
|
constexpr uint16_t WIFI_HOST = 0x0602; // string (max 63 chars)
|
|
|
|
|
|
constexpr uint16_t WIFI_PORT = 0x0603; // uint16
|
|
|
|
|
|
constexpr uint16_t WIFI_PATH = 0x0604; // string (max 31 chars)
|
|
|
|
|
|
|
|
|
|
|
|
constexpr uint16_t WIFI_FIRST = 0x0600;
|
|
|
|
|
|
constexpr uint16_t WIFI_LAST = 0x0604;
|
|
|
|
|
|
constexpr uint16_t WIFI_COUNT = WIFI_LAST - WIFI_FIRST + 1;
|
2026-02-07 16:21:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 15:51:20 +00:00
|
|
|
|
// Tuneable settings - all values exposed for external adjustment
|
|
|
|
|
|
struct FocusSettings {
|
|
|
|
|
|
// Motor IDs
|
|
|
|
|
|
uint8_t eyeMotor1 = 14;
|
|
|
|
|
|
uint8_t eyeMotor2 = 15;
|
|
|
|
|
|
uint8_t neckMotor = 27;
|
|
|
|
|
|
|
|
|
|
|
|
// Eye motor position range
|
|
|
|
|
|
uint16_t eyeCenter = 2200;
|
|
|
|
|
|
uint16_t eyeMin = 1700;
|
|
|
|
|
|
uint16_t eyeMax = 2500;
|
|
|
|
|
|
|
|
|
|
|
|
// Neck motor position range
|
|
|
|
|
|
uint16_t neckCenter = 2000;
|
|
|
|
|
|
uint16_t neckMin = 1000;
|
|
|
|
|
|
uint16_t neckMax = 3000;
|
|
|
|
|
|
|
|
|
|
|
|
// Face detection x range (pixels, center-relative)
|
|
|
|
|
|
float faceXMin = -140.0f;
|
|
|
|
|
|
float faceXMax = 140.0f;
|
|
|
|
|
|
|
|
|
|
|
|
// Interpolation speeds (0-1 per update, higher = faster)
|
|
|
|
|
|
float eyeSpeed = 0.15f; // Eyes dart quickly to target
|
|
|
|
|
|
float neckSpeed = 0.02f; // Neck follows smoothly / slowly
|
|
|
|
|
|
float eyeReturnSpeed = 0.05f; // Speed eyes center as neck catches up
|
|
|
|
|
|
|
|
|
|
|
|
// Neck starts moving after this delay (ms) from first detecting a target
|
|
|
|
|
|
unsigned long neckDelayMs = 500;
|
|
|
|
|
|
|
|
|
|
|
|
// How much of the face offset the neck should try to cover (0-1)
|
|
|
|
|
|
// 1.0 = neck tries to fully face the target, 0.5 = neck covers half
|
|
|
|
|
|
float neckContribution = 0.7f;
|
|
|
|
|
|
|
|
|
|
|
|
// Invert neck direction (set true if neck motor is wired backwards)
|
|
|
|
|
|
bool neckInvert = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Return-to-center speeds when no face detected
|
|
|
|
|
|
float eyeCenteringSpeed = 0.03f;
|
|
|
|
|
|
float neckCenteringSpeed = 0.02f;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-20 16:32:05 +00:00
|
|
|
|
class FocusBehavior : public Behavior {
|
|
|
|
|
|
public:
|
|
|
|
|
|
FocusBehavior();
|
|
|
|
|
|
|
2026-02-07 15:51:20 +00:00
|
|
|
|
// Update behavior - check face detection for targets
|
2026-01-20 16:32:05 +00:00
|
|
|
|
bool update() override;
|
|
|
|
|
|
|
|
|
|
|
|
// Get motor position for a controlled motor
|
|
|
|
|
|
bool getMotorPosition(uint8_t motorID, uint16_t& position) override;
|
|
|
|
|
|
|
2026-02-07 15:51:20 +00:00
|
|
|
|
// Access settings for external tuning
|
|
|
|
|
|
FocusSettings& getSettings() { return settings; }
|
|
|
|
|
|
const FocusSettings& getSettings() const { return settings; }
|
|
|
|
|
|
|
2026-01-20 16:32:05 +00:00
|
|
|
|
private:
|
2026-02-07 15:51:20 +00:00
|
|
|
|
FocusSettings settings;
|
|
|
|
|
|
|
2026-01-20 16:32:05 +00:00
|
|
|
|
bool isActive;
|
2026-01-21 07:36:07 +00:00
|
|
|
|
|
2026-02-07 15:51:20 +00:00
|
|
|
|
// Current smoothed positions (servo units)
|
|
|
|
|
|
uint16_t eyePosition;
|
|
|
|
|
|
uint16_t neckPosition;
|
2026-01-21 07:36:07 +00:00
|
|
|
|
|
2026-02-07 15:51:20 +00:00
|
|
|
|
// Current normalized offset the neck has reached (-1 to +1)
|
|
|
|
|
|
// This tracks how far the neck has rotated toward the target
|
|
|
|
|
|
float neckNormalized;
|
|
|
|
|
|
|
2026-01-21 07:36:07 +00:00
|
|
|
|
// Timing
|
2026-02-07 15:51:20 +00:00
|
|
|
|
unsigned long faceDetectedTime; // When face was first seen (for neck delay)
|
|
|
|
|
|
bool faceWasPresent; // Was a face present last frame?
|
|
|
|
|
|
|
|
|
|
|
|
// Map a normalized value (-1..+1) to an asymmetric servo range
|
|
|
|
|
|
uint16_t normalizedToServo(float n, uint16_t center, uint16_t min, uint16_t max) const;
|
|
|
|
|
|
|
|
|
|
|
|
// Smooth interpolation helpers
|
|
|
|
|
|
static float lerpf(float current, float target, float t);
|
|
|
|
|
|
static uint16_t lerp(uint16_t current, uint16_t target, float t);
|
2026-01-20 16:32:05 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-21 14:43:12 +00:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Idle Behavior - Adds perlin noise to all motors for natural idle motion
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class IdleBehavior : public Behavior {
|
|
|
|
|
|
public:
|
|
|
|
|
|
IdleBehavior();
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize with list of motor IDs to control
|
|
|
|
|
|
void initMotors(const std::vector<uint8_t>& motorIDs);
|
|
|
|
|
|
|
|
|
|
|
|
// Update behavior - calculates new noise positions
|
|
|
|
|
|
bool update() override;
|
|
|
|
|
|
|
|
|
|
|
|
// Get motor position for a controlled motor
|
|
|
|
|
|
bool getMotorPosition(uint8_t motorID, uint16_t& position) override;
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
// Store current positions for each motor (indexed by motor ID)
|
|
|
|
|
|
uint16_t motorPositions[256];
|
|
|
|
|
|
|
|
|
|
|
|
// Time offset for perlin noise animation
|
|
|
|
|
|
unsigned long startTime;
|
|
|
|
|
|
|
|
|
|
|
|
// Configuration
|
|
|
|
|
|
static constexpr uint16_t POSITION_CENTER = 2047;
|
|
|
|
|
|
static constexpr uint16_t NOISE_RANGE = 100; // ±500 from center
|
|
|
|
|
|
static constexpr float NOISE_SPEED = 0.000125f; // How fast noise evolves (slower = smoother, 4x slower)
|
|
|
|
|
|
static constexpr uint16_t MOTOR_SEED_OFFSET = 100; // Seed offset between motors for variety
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-25 06:44:08 +00:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Viseme Behavior - Controls mouth motors for speech
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
// Motor position within a viseme
|
|
|
|
|
|
struct VisemeMotorPosition {
|
|
|
|
|
|
uint8_t motorID;
|
|
|
|
|
|
uint16_t position;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Viseme definition: ID, label (3 chars), and motor positions
|
|
|
|
|
|
struct Viseme {
|
|
|
|
|
|
uint8_t id;
|
|
|
|
|
|
char label[4]; // 3 characters + null terminator
|
|
|
|
|
|
std::vector<VisemeMotorPosition> motorPositions;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class VisemeBehavior : public Behavior {
|
|
|
|
|
|
public:
|
|
|
|
|
|
VisemeBehavior();
|
|
|
|
|
|
|
|
|
|
|
|
// Add a viseme with a 3-char label (auto-assigns ID)
|
|
|
|
|
|
// Returns the assigned viseme ID
|
|
|
|
|
|
uint8_t addViseme(const char* label);
|
|
|
|
|
|
|
|
|
|
|
|
// Legacy: Add a viseme with specific ID and motor positions (for backwards compatibility)
|
|
|
|
|
|
void addViseme(uint8_t id, uint16_t pos40, uint16_t pos43, uint16_t pos44);
|
|
|
|
|
|
|
2026-01-25 09:33:08 +00:00
|
|
|
|
// Add a viseme with specific ID, label, and motor positions
|
|
|
|
|
|
void addViseme(uint8_t id, const char* label, uint16_t pos40, uint16_t pos43, uint16_t pos44);
|
|
|
|
|
|
|
2026-01-25 06:44:08 +00:00
|
|
|
|
// Delete a viseme by ID
|
|
|
|
|
|
// Returns true if deleted, false if not found
|
|
|
|
|
|
bool deleteViseme(uint8_t visemeID);
|
|
|
|
|
|
|
|
|
|
|
|
// Set motor positions for a viseme
|
|
|
|
|
|
// Returns true if viseme found and updated, false otherwise
|
|
|
|
|
|
bool setVisemeMotors(uint8_t visemeID, const std::vector<VisemeMotorPosition>& positions);
|
|
|
|
|
|
|
2026-01-25 09:33:08 +00:00
|
|
|
|
// Set motor positions and label for a viseme
|
|
|
|
|
|
// Returns true if viseme found and updated, false otherwise
|
|
|
|
|
|
bool setVisemeMotorsAndLabel(uint8_t visemeID, const char* label, const std::vector<VisemeMotorPosition>& positions);
|
|
|
|
|
|
|
2026-01-31 08:05:19 +00:00
|
|
|
|
// Create or update a viseme with specific ID, label, and motor positions
|
|
|
|
|
|
// Returns true on success
|
|
|
|
|
|
bool createOrUpdateViseme(uint8_t visemeID, const char* label, const std::vector<VisemeMotorPosition>& positions);
|
|
|
|
|
|
|
2026-01-25 06:44:08 +00:00
|
|
|
|
// Get all visemes (for VLST command)
|
|
|
|
|
|
const std::vector<Viseme>& getVisemes() const { return visemes; }
|
|
|
|
|
|
|
|
|
|
|
|
// Trigger a viseme by ID - activates the behavior and sets positions
|
|
|
|
|
|
// Returns true if viseme was found, false otherwise
|
|
|
|
|
|
bool triggerViseme(uint8_t visemeID);
|
|
|
|
|
|
|
|
|
|
|
|
// Update behavior - checks for timeout and deactivates
|
|
|
|
|
|
bool update() override;
|
|
|
|
|
|
|
|
|
|
|
|
// Get motor position for a controlled motor
|
|
|
|
|
|
bool getMotorPosition(uint8_t motorID, uint16_t& position) override;
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
bool isActive;
|
|
|
|
|
|
unsigned long lastTriggerTime;
|
|
|
|
|
|
uint8_t nextVisemeID; // Auto-increment ID for new visemes
|
|
|
|
|
|
|
|
|
|
|
|
// Current active motor positions (when triggered)
|
|
|
|
|
|
std::vector<VisemeMotorPosition> currentPositions;
|
|
|
|
|
|
|
|
|
|
|
|
// Registered visemes
|
|
|
|
|
|
std::vector<Viseme> visemes;
|
|
|
|
|
|
|
|
|
|
|
|
// Configuration
|
2026-02-09 16:35:47 +00:00
|
|
|
|
static constexpr unsigned long TIMEOUT_MS = 200; // 3 second timeout
|
2026-01-25 06:44:08 +00:00
|
|
|
|
static constexpr uint16_t DEFAULT_POSITION = 2047; // Center/rest position
|
|
|
|
|
|
|
|
|
|
|
|
// Helper to find viseme by ID
|
|
|
|
|
|
Viseme* findViseme(uint8_t id);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-20 16:32:05 +00:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Behavior Manager - Manages active behaviors and resolves motor conflicts
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class BehaviorManager {
|
|
|
|
|
|
public:
|
|
|
|
|
|
BehaviorManager();
|
|
|
|
|
|
|
2026-01-21 06:49:48 +00:00
|
|
|
|
// Add a behavior to the manager with an ID
|
|
|
|
|
|
void addBehavior(BehaviorID id, Behavior* behavior);
|
2026-01-20 16:32:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Remove a behavior from the manager
|
|
|
|
|
|
void removeBehavior(Behavior* behavior);
|
|
|
|
|
|
|
2026-01-21 06:49:48 +00:00
|
|
|
|
// Enable/disable a behavior by ID
|
|
|
|
|
|
void setBehaviorEnabled(BehaviorID id, bool enabled);
|
|
|
|
|
|
|
|
|
|
|
|
// Check if a behavior is enabled
|
|
|
|
|
|
bool isBehaviorEnabled(BehaviorID id) const;
|
|
|
|
|
|
|
|
|
|
|
|
// Get count of registered behaviors
|
|
|
|
|
|
uint8_t getBehaviorCount() const;
|
|
|
|
|
|
|
|
|
|
|
|
// Get behavior info at index (for iteration)
|
|
|
|
|
|
// Returns true if index is valid and fills out id and enabled
|
|
|
|
|
|
bool getBehaviorInfo(uint8_t index, BehaviorID& id, bool& enabled) const;
|
|
|
|
|
|
|
|
|
|
|
|
// Update all enabled behaviors (call each frame)
|
2026-01-20 16:32:05 +00:00
|
|
|
|
void update();
|
|
|
|
|
|
|
|
|
|
|
|
// Check if a behavior wants to control a specific motor
|
|
|
|
|
|
// Returns true if a behavior provides a position, false otherwise
|
|
|
|
|
|
bool getMotorPosition(uint8_t motorID, uint16_t& position);
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
2026-01-21 06:49:48 +00:00
|
|
|
|
struct BehaviorEntry {
|
|
|
|
|
|
BehaviorID id;
|
|
|
|
|
|
Behavior* behavior;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<BehaviorEntry> behaviors;
|
|
|
|
|
|
bool enabledStates[256] = {false}; // Track enabled state by ID
|
2026-01-20 16:32:05 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Global behavior manager instance
|
|
|
|
|
|
extern BehaviorManager behaviorManager;
|
2026-01-25 06:44:08 +00:00
|
|
|
|
|
2026-02-07 16:21:32 +00:00
|
|
|
|
// Global behavior instances (for command/config access)
|
|
|
|
|
|
extern FocusBehavior focusBehavior;
|
2026-01-25 06:44:08 +00:00
|
|
|
|
extern VisemeBehavior visemeBehavior;
|