Construcción del animatronic
Construcción del Animatronic
Aqui demuestro como la construccion del animatronic es legitima (y que no utilice magia negra para su funcionamiento jaja)
La construcción del animatronic empezó hace aproximadamente 2 meses, el animatronic es la continuación de un proyecto en el que ya llevo bastante tiempo para el mismo fin.
Del proyecto reutilizó el chasis (tronco) del animatronic, al que se le cambio las placas de poliuretano (reciclado), después, se le hizo un rediseño completo al mecanismo del cuello, aunque reutilice el mecanismo tipo rótula, para lograr que el animatronic pudiera mover el cuello. Utilicé una fuente de poder reciclada de UCP (Ucp es un grupo estudiantil donde pertenezco donde se reciclan los electrónicos para preservar el medio ambiente el sitio web es UCP)
Despues se colocaron los servomotores para fijar todo lo que es el rostro y cuello.
Ahora, para el mecanismo de los ojos diseñé por mi cuenta un mecanismo similar al de la rotula para lograr que el animatronic tuviera la mejor movilidad, el diseño requiere mejoras ya que es la primera parte, pero al menos para movilidad sencilla (movimiento de ojos de lado a lado y parpadeo) es totalmente funcional
Se imprimió en 3D los mecanismos y por si a alguna persona le interesa replicarlo, detallaré aqui abajo los archivos:
ARCHIVOS STL PARA IMPRIMIR
Muestra del funcionamiento de los servomotores y mecanismos (de movilidad, no del habla):
PROGRAMACIÓN
El programa está escrito en Python. Usando una libreria que vincula Character AI, una pagina web donde creas personalidades diferentes, eso facilita el tener que programar una IA con una personalidad especifica.
La documentación básica de la libreria se encuentra aqui:
Aio CAI
El programa adaptado a mis necesidades es el siguiente:
El programa adaptado a mis necesidades es el siguiente:
from characterai import aiocai
import serial
import speech_recognition as sr
import pyttsx3
import asyncio
# Inicializar reconocimiento de voz y sintetizador
recognizer = sr.Recognizer()
engine = pyttsx3.init()
# Configurar Arduino
try:
arduino = serial.Serial("COM11", 9600)
print("✅ Arduino conectado")
except Exception as e:
print(f"❌ Error al conectar Arduino: {e}")
# Configuración de voz
voices = engine.getProperty('voices')
engine.setProperty('voice', voices[3].id)
def get_voice_input():
with sr.Microphone() as source:
print("Escuchando...")
recognizer.adjust_for_ambient_noise(source)
audio = recognizer.listen(source)
try:
text = recognizer.recognize_google(audio, language="es-ES")
print(f"Has dicho: {text}")
return text
except sr.UnknownValueError:
print("No se pudo entender el audio")
return None
except sr.RequestError as e:
print(f"Error en el servicio de reconocimiento de voz: {e}")
return None
async def main():
char = input('CHAR ID: ')
client = aiocai.Client('LA ID QUE OBTIENES AL CREAR TU CUENTA')
me = await client.get_me()
async with await client.connect() as chat:
new, answer = await chat.new_chat(char, me.id)
print(f"✅ Chat iniciado con {answer.name}: {answer.text}")
while True:
voice_input = get_voice_input()
if voice_input is None:
continue
message = await chat.send_message(char, new.chat_id, voice_input)
# Reproducimos la respuesta en audio
engine.say(message.text)
arduino.write(b'h') # Activar Arduino
engine.runAndWait()
arduino.write(b's') # Desactivar Arduino
print(f"{message.name}: {message.text}")
if __name__ == "__main__":
asyncio.run(main())
En español? Jaja, bueno esto es lo que hace:
- Importación de bibliotecas: Se importan las bibliotecas necesarias para el reconocimiento de voz, síntesis de texto a voz, comunicación serial y el cliente de Character AI.
- Inicialización de componentes: Se inicializan el reconocedor de voz de google y el motor de síntesis de voz.
- Conexión con Arduino: Se intenta establecer una conexión serial con Arduino en el puerto COM11(puerto de la computadora) a 9600 baudios (canal, esto es mas que nada por parte de Arduino). (Esto es primordial, sino no hay movimiento en la boca)
- Configuración de voz: Se selecciona una voz específica para la síntesis de texto a voz. (Puede ser mujer u hombre, en este caso pues, mujer)
- Definición de la función get_voice_input(): Esta función captura la entrada de voz del usuario, la procesa y la convierte en texto.
- Inicio de la función principal (main()): Se solicita al usuario que ingrese un ID de personaje y se inicializa el cliente de Character AI.
- Inicio de la conversación: Se establece una nueva conversación con el personaje seleccionado. En este caso, la pascualita
- Bucle principal (aqui sucede la magia): Se inicia un bucle infinito que realiza las siguientes acciones:
- a. Captura la entrada de voz del usuario.
- b. Envía el mensaje de voz convertido a texto al personaje de IA.
- c. Recibe la respuesta del personaje.
- d. La respuesta del personaje se convierte en audio y se reproduce.
Al final: Se envían señales a un Arduino para activarlo antes de reproducir el audio
y desactivarlo después. (Estas señales son una "h" para indicar que habla, y un
"s" para indicar silencio, o que deje de hablar
AHORA, del lado del Arduino:
#include <Servo.h>
Servo myservo;
int servoPin = 3; // Pin conectado al servo
boolean talking = false; // Estado del loop
void setup() {
Serial.begin(9600);
myservo.attach(servoPin); // Inicializar el servo
}
void loop() {
if (Serial.available()>0) {
char option = Serial.read();
if (option == 'h') {
talking = true;
} else if (option == 's') {
talking = false;
}
}
if (talking) {
myservo.write(90);
delay(100);
myservo.write(80);
delay(100);
}
if (talking==false) {
myservo.write(90);
}
}
El código se traduce así:
El código es el siguiente:
- Se importa la librería de Servo, para el arduino poder controlar el mismo
- Se declara que se utilizará un servomotor llamado "miservo"
- Se declará una variable booleana, que ayudara a hacer un bucle para el servomotor
- Se inicia el puerto serial a la frecuencia 9600 (para recibir señales del código en python)
- Se espera a recibir una señal en serial, ya sea "h" o "s"
- Si la señal es "h", la variable booleana se volverá verdadera y entrara en bucle el servo entre los ángulos 90° y 80°, para simular el habla
- Si la señal es "s" la variable se volverá falsa y el servomotor se alineará en 90° (simulando la boca cerrada)
POR ULTIMA INSTANCIA: LA MOVILIDAD:
Esta es un poco mas tedioso de explicar pero no se preocupen, aquí
el que se rompió la cabeza fui yo (nuevamente)
Utilice para el cuello 3 servos y para los ojos 2 servos más, aunque solo utilizaré 2 para este
proyecto. Además, utilicé 2 joystick para así lograr moverlo a mi criterio (y asustar a la gente jeje
), además los joystick tienen botones para agregar funciones, en este caso solo utilicé 1.
#include <Servo.h>
Servo servoCuelloLadoALado;
Servo servoArribaAbajo;
Servo servoCuelloDiagonal;
Servo servoOjosLadoALado;
Servo servoNuevo;
// Pines analógicos para los joysticks
const int joyX1 = A0;
const int joyY1 = A1;
const int joyX2 = A2;
const int joyY2 = A3;
// Pin digital para el botón del joystick 2
const int botonJoy2 = 2;
// Pines digitales para los servos
const int pinServoCuelloLadoALado = 3;
const int pinServoArribaAbajo = 5;
const int pinServoCuelloDiagonal = 6;
const int pinServoOjosLadoALado = 9;
const int pinServoNuevo = 10;
// Variables para la calibración de los joysticks
int centroJoyX1, centroJoyY1, centroJoyX2, centroJoyY2;
const int muestrasCalibration = 10;
const int umbralJoystick = 20; // Ajusta este valor según sea necesario
void setup()
{
Serial.begin(9600);
servoCuelloLadoALado.attach(pinServoCuelloLadoALado);
servoArribaAbajo.attach(pinServoArribaAbajo);
servoCuelloDiagonal.attach(pinServoCuelloDiagonal);
servoOjosLadoALado.attach(pinServoOjosLadoALado);
servoNuevo.attach(pinServoNuevo);
pinMode(botonJoy2, INPUT_PULLUP);
// Calibrar los joysticks
calibrarJoysticks();
// Inicializar todos los servos en 90 grados
servoCuelloLadoALado.write(90);
servoArribaAbajo.write(90);
servoCuelloDiagonal.write(90);
servoOjosLadoALado.write(90);
servoNuevo.write(90);
delay(5000);
}
void calibrarJoysticks() {
long sumaX1 = 0, sumaY1 = 0, sumaX2 = 0, sumaY2 = 0;
for (int i = 0; i < muestrasCalibration; i++) {
sumaX1 += analogRead(joyX1);
sumaY1 += analogRead(joyY1);
sumaX2 += analogRead(joyX2);
sumaY2 += analogRead(joyY2);
delay(1000);
}
centroJoyX1 = sumaX1 / muestrasCalibration;
centroJoyY1 = sumaY1 / muestrasCalibration;
centroJoyX2 = sumaX2 / muestrasCalibration;
centroJoyY2 = sumaY2 / muestrasCalibration;
Serial.println("Calibracion completada:");
Serial.print("Centro JoyX1: "); Serial.println(centroJoyX1);
Serial.print("Centro JoyY1: "); Serial.println(centroJoyY1);
Serial.print("Centro JoyX2: "); Serial.println(centroJoyX2);
Serial.print("Centro JoyY2: "); Serial.println(centroJoyY2);
}
int mapearJoystickAServo(int valorJoystick, int centroCalibracion) {
int diferencia = valorJoystick - centroCalibracion;
if (abs(diferencia) < umbralJoystick) {
return 90; // Posición central
} else {
return map(constrain(diferencia, -512, 512), -512, 512, 65, 135);
}
}
void imprimirEstadoServos(int cuelloLado, int arribaAbajo, int cuelloDiagonal, int ojosLado, int nuevo) {
Serial.println("Estado de los servos:");
Serial.print("Cuello lado a lado: "); Serial.println(cuelloLado);
Serial.print("Arriba y abajo: "); Serial.println(arribaAbajo);
Serial.print("Cuello en diagonal: "); Serial.println(cuelloDiagonal);
Serial.print("Ojos lado a lado: "); Serial.println(ojosLado);
Serial.print("Nuevo servo: "); Serial.println(nuevo);
Serial.println("--------------------");
}
void loop()
{
int valorJoyX1 = analogRead(joyX1);
int valorJoyY1 = analogRead(joyY1);
int valorJoyX2 = analogRead(joyX2);
int valorJoyY2 = analogRead(joyY2);
int estadoBotonJoy2 = digitalRead(botonJoy2);
int anguloCuelloLadoALado = mapearJoystickAServo(valorJoyX1, centroJoyX1);
int anguloArribaAbajo = mapearJoystickAServo(valorJoyY1, centroJoyY1);
int anguloCuelloDiagonal = mapearJoystickAServo(valorJoyY2, centroJoyY2);
int anguloOjosLadoALado = mapearJoystickAServo(valorJoyX2, centroJoyX2);
servoCuelloLadoALado.write(anguloCuelloLadoALado);
servoArribaAbajo.write(anguloArribaAbajo);
servoCuelloDiagonal.write(anguloCuelloDiagonal);
servoOjosLadoALado.write(anguloOjosLadoALado);
int anguloNuevoServo = (estadoBotonJoy2 == LOW) ? 40 : 90;
servoNuevo.write(anguloNuevoServo);
imprimirEstadoServos(anguloCuelloLadoALado, anguloArribaAbajo, anguloCuelloDiagonal, anguloOjosLadoALado, anguloNuevoServo);
delay(1000);
}
En pocas palabras, el programa calibra los joystick, mapea el movimiento de los joystick (como un control de videojuego) y lo refleja en ángulos para los servomotores, en este caso en un rango de 65 a 135 grados, y para el botón solamente cuando el botón del joystick derecho está presionado, mueve el servomotor que hace que parpadeé de 90 grados a 40 grados. Todo eso lo hace en un bucle de 1 segundo de retardo (para que no sea tan sensible el sistema y no salga volando nada jaja).
Todo eso junto, forma al animatrónico de la pascualita, haciendo su correcto funcionamiento. No fue magia negra al final de cuentas xD.
Los últimos detalles del animatrónico fueron estéticos, pero ese es el resumen del funcionamiento. :D