Skip to main content

Overview

Utility functions supporting the Grupo de Anda AI system:
  • Roku Device Control - TV and streaming device management
  • Network Scanning - Automatic device discovery
  • Audio Playback - Text-to-speech and voice output
  • Text Processing - String manipulation and formatting

Roku Device Control

Functions for controlling Roku TV devices over the local network.

Configuration

Roku device settings and application mappings.
# Target Device
TARGET_IP = "192.168.1.8"  # Primary Roku TV IP address

# Roku Application IDs
ROKU_APPS = {
    "netflix": "12",
    "youtube": "837",
    "prime": "13",
    "disney": "291097",
    "spotify": "199",
    "hbo": "61322",
    "plex": "3847",
    "television": "tvinput.dtv",
    "canales": "tvinput.dtv",
    "antena": "tvinput.dtv",
    "tele": "tvinput.dtv"
}

enviar_comando_roku()

Sends HTTP commands to Roku device.
ip
string
required
IP address of the Roku device
endpoint
string
required
API endpoint (e.g., “keypress/Home”, “launch/12”)
metodo
string
default:"POST"
HTTP method - “POST” or “GET”
success
bool
Returns True if command succeeded, False otherwise
import requests

def enviar_comando_roku(ip, endpoint, metodo="POST"):
    """Send command to Roku device."""
    url = f"http://{ip}:8060/{endpoint}"
    try:
        if metodo == "POST":
            requests.post(url, timeout=5)
        else:
            requests.get(url, timeout=5)
        return True
    except:
        return False

# Examples
ip = "192.168.1.8"

# Launch Netflix
enviar_comando_roku(ip, "launch/12")

# Press Home button
enviar_comando_roku(ip, "keypress/Home")

# Power off
enviar_comando_roku(ip, "keypress/PowerOff")

# Volume up
enviar_comando_roku(ip, "keypress/VolumeUp")
Common Endpoints:
  • launch/{app_id} - Launch application
  • install/{app_id} - Install application
  • query/active-app - Get current app
  • query/apps - List installed apps

obtener_app_actual()

Retrieves the currently active application on Roku device.
ip
string
required
IP address of the Roku device
app_name
string
Name of the active application, or “el Menú de Inicio” if on home screen, or “desconocida” if unable to determine
def obtener_app_actual(ip):
    """Get currently active app on Roku."""
    url = f"http://{ip}:8060/query/active-app"
    try:
        response = requests.get(url, timeout=1.5)
        if response.status_code == 200:
            match = re.search(r'<app.*?>(.*?)</app>', response.text)
            if match:
                app_name = match.group(1)
                return app_name if app_name != "Roku" else "el Menú de Inicio"
    except:
        pass
    return "desconocida"

# Example
ip = "192.168.1.8"
app = obtener_app_actual(ip)
print(f"Current app: {app}")

ejecutar_busqueda_roku()

Executes a search query in a specific Roku application.
ip
string
required
IP address of the Roku device
app_name
string
required
Name of the application to search in (e.g., “netflix”, “youtube”)
query
string
required
Search query text
success
bool
Returns True if search was executed successfully
import requests
import time

def ejecutar_busqueda_roku(ip, app_name, query):
    """Execute search in Roku app."""
    # Get app ID from mapping
    app_id = ROKU_APPS.get(app_name.lower(), "837")  # Default to YouTube
    
    if app_id:
        # Launch the app
        enviar_comando_roku(ip, f"launch/{app_id}")
        time.sleep(2)  # Wait for app to load
        
        # URL encode the query
        query_encoded = requests.utils.quote(query)
        
        # Execute search
        endpoint = f"search/browse?keyword={query_encoded}&launch=true&provider-id={app_id}"
        return enviar_comando_roku(ip, endpoint)
    
    return False

# Examples
ip = "192.168.1.8"

# Search YouTube
ejecutar_busqueda_roku(ip, "youtube", "recetas de cocina")

# Search Netflix
ejecutar_busqueda_roku(ip, "netflix", "películas de acción")

# Search with special characters
ejecutar_busqueda_roku(ip, "prime", "El Señor de los Anillos")

Network Scanning

Automatic discovery of Roku devices on the local network.

verificar_roku_por_info()

Verifies if a device at given IP is a Roku device.
ip
string
required
IP address to check
device_info
dict|None
Returns device information dict with ‘ip’ and ‘tipo’ keys, or None if not a Roku device
import requests
import re

def verificar_roku_por_info(ip):
    """Check if device is a Roku."""
    url = f"http://{ip}:8060/query/device-info"
    try:
        response = requests.get(url, timeout=1.5)
        if response.status_code == 200 and "<device-info>" in response.text:
            # Extract model name
            model_match = re.search(
                r'<model-name>(.*?)</model-name>',
                response.text
            )
            model_name = model_match.group(1) if model_match else "Roku"
            
            return {
                "ip": ip,
                "tipo": f"Roku ({model_name})"
            }
    except:
        pass
    return None

# Usage
device = verificar_roku_por_info("192.168.1.8")
if device:
    print(f"Found: {device['tipo']} at {device['ip']}")
else:
    print("No Roku device found")

escanear_red_tvs()

Scans local network for Roku devices.
dispositivos
list[dict]
List of discovered devices with ‘ip’ and ‘tipo’ keys
import socket
import threading
import time

def escanear_red_tvs(target_ip="192.168.1.8"):
    """Scan network for Roku devices."""
    encontrados = []
    
    # Try target IP first
    tv_principal = verificar_roku_por_info(target_ip)
    if tv_principal:
        encontrados.append(tv_principal)
        return encontrados
    
    try:
        # Get local IP to determine network range
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        ip_local = s.getsockname()[0]
        s.close()
        
        # Extract network prefix (e.g., "192.168.1")
        prefijo = ".".join(ip_local.split(".")[:-1])
        
        # Scan function for threading
        def tarea_escaneo(ip_test):
            res = verificar_roku_por_info(ip_test)
            if res:
                encontrados.append(res)
        
        # Scan all IPs in range
        hilos = []
        for i in range(1, 255):
            t = threading.Thread(
                target=tarea_escaneo,
                args=(f"{prefijo}.{i}",)
            )
            t.daemon = True
            t.start()
            hilos.append(t)
        
        # Wait for scan to complete
        time.sleep(1.5)
        
    except:
        pass
    
    return encontrados

# Usage
devices = escanear_red_tvs()
print(f"Found {len(devices)} Roku device(s):")
for device in devices:
    print(f"  {device['tipo']} at {device['ip']}")

Audio Playback

Text-to-speech and audio output utilities.

hablar_local()

Generates speech from text using Google Text-to-Speech.
texto
string
required
Text to convert to speech (automatically cleaned)
import os
import time
import re
from gtts import gTTS
import pygame

def limpiar_texto_para_audio(texto):
    """Clean text for TTS."""
    # Remove command tags
    texto = re.sub(r'/\*.*?\*/', '', texto)
    # Remove markdown
    texto = texto.replace('*', '').replace('#', '').replace('_', ' ')
    # Remove URLs
    texto = re.sub(r'http\S+', '', texto)
    # Normalize whitespace
    texto = " ".join(texto.split())
    return texto

def hablar_local(texto):
    """Speak text using gTTS."""
    texto_limpio = limpiar_texto_para_audio(texto)
    if not texto_limpio:
        return
    
    try:
        # Generate speech
        tts = gTTS(text=texto_limpio, lang='es', slow=False)
        filename = "temp_voice.mp3"
        tts.save(filename)
        
        # Play audio
        pygame.mixer.init()
        pygame.mixer.music.load(filename)
        pygame.mixer.music.play()
        
        # Wait for playback to finish
        while pygame.mixer.music.get_busy():
            time.sleep(0.1)
        
        # Cleanup
        pygame.mixer.music.unload()
        if os.path.exists(filename):
            os.remove(filename)
            
    except Exception as e:
        print(f"Audio error: {e}")

# Usage
hablar_local("Hola Rosario, ¿cómo estás hoy?")
gTTS Parameters:
lang
string
default:"es"
Language code (“es” for Spanish, “en” for English, etc.)
slow
bool
default:false
Whether to use slow speech rate
tld
string
default:"com"
Top-level domain for accent variation (e.g., “com.mx” for Mexican Spanish)

limpiar_texto_para_audio()

Cleans text before TTS conversion.
texto
string
required
Raw text with potential markdown, URLs, and command tags
texto_limpio
string
Cleaned text ready for text-to-speech
Example
raw = "/*app(netflix)*/ **Abriendo** Netflix para ti. https://netflix.com #tv"
cleaned = limpiar_texto_para_audio(raw)
print(cleaned)  # "Abriendo Netflix para ti."
Cleaning Steps:
  1. Remove command tags (/*...*/)
  2. Remove markdown formatting (*, #, _)
  3. Remove URLs (http/https links)
  4. Normalize whitespace (collapse multiple spaces)

Text Processing

String manipulation utilities.

Command Tag Extraction

Extract and process embedded command tags from AI responses.
import re

def extract_command_tags(text):
    """Extract all command tags from text."""
    tags = re.findall(r'/\*(.*?)\*/', text)
    return tags

# Example
text = "Claro Rosario. /*app(netflix)*/ Abriendo Netflix para ti."
tags = extract_command_tags(text)
print(tags)  # ['app(netflix)']

Remove Tags

Remove command tags from text for display.
import re

def remove_command_tags(text):
    """Remove all command tags from text."""
    return re.sub(r'/\*.*?\*/', '', text).strip()

# Example
text = "/*app(netflix)*/ Abriendo Netflix para ti."
cleaned = remove_command_tags(text)
print(cleaned)  # "Abriendo Netflix para ti."

Memory Management

Conversation history management.

Conversation History

Manage conversation context for AI models.
class ConversationHistory:
    def __init__(self, max_length=10):
        self.history = []
        self.max_length = max_length
    
    def add(self, user_input, ai_response):
        """Add conversation turn."""
        self.history.append({
            'u': user_input,
            'k': ai_response
        })
        
        # Trim if too long
        if len(self.history) > self.max_length:
            self.history.pop(0)
    
    def get_formatted(self):
        """Get formatted history for API."""
        formatted = []
        for turn in self.history:
            formatted.append({
                'role': 'user',
                'content': turn['u']
            })
            formatted.append({
                'role': 'assistant',
                'content': turn['k']
            })
        return formatted
    
    def clear(self):
        """Clear history."""
        self.history = []

# Usage
history = ConversationHistory(max_length=10)
history.add("Hola", "Hola Rosario, ¿cómo está?")
history.add("Abre Netflix", "Abriendo Netflix para ti.")

formatted = history.get_formatted()
print(f"History length: {len(formatted)} messages")

Error Handling

import requests
from requests.exceptions import Timeout, ConnectionError

def safe_roku_command(ip, endpoint, retries=3):
    """Execute Roku command with retry logic."""
    for attempt in range(retries):
        try:
            url = f"http://{ip}:8060/{endpoint}"
            response = requests.post(url, timeout=5)
            return True
        except Timeout:
            print(f"Timeout on attempt {attempt + 1}")
        except ConnectionError:
            print(f"Connection failed on attempt {attempt + 1}")
        except Exception as e:
            print(f"Unexpected error: {e}")
    
    return False
import pygame
from gtts import gTTS

def safe_speak(text):
    """TTS with error handling."""
    try:
        tts = gTTS(text=text, lang='es')
        tts.save('temp.mp3')
        
        pygame.mixer.init()
        pygame.mixer.music.load('temp.mp3')
        pygame.mixer.music.play()
        
        while pygame.mixer.music.get_busy():
            time.sleep(0.1)
            
    except ImportError:
        print("gTTS or pygame not installed")
    except Exception as e:
        print(f"TTS error: {e}")
    finally:
        try:
            pygame.mixer.music.unload()
            if os.path.exists('temp.mp3'):
                os.remove('temp.mp3')
        except:
            pass
def safe_network_scan(timeout=2.0):
    """Network scan with error handling."""
    devices = []
    
    try:
        # Try to get local IP
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.settimeout(timeout)
        s.connect(("8.8.8.8", 80))
        local_ip = s.getsockname()[0]
        s.close()
        
        prefix = ".".join(local_ip.split(".")[:-1])
        
        # Scan network
        for i in range(1, 255):
            ip = f"{prefix}.{i}"
            device = verificar_roku_por_info(ip)
            if device:
                devices.append(device)
                
    except socket.timeout:
        print("Network timeout during scan")
    except socket.error as e:
        print(f"Socket error: {e}")
    except Exception as e:
        print(f"Scan error: {e}")
    
    return devices

Common Patterns

Device Control

# Complete device control
devices = escanear_red_tvs()
if devices:
    ip = devices[0]['ip']
    ejecutar_busqueda_roku(
        ip, "netflix", "stranger things"
    )

Voice Output

# Clean and speak response
response = "/*app(netflix)*/ Listo!"
clean = remove_command_tags(response)
hablar_local(clean)

Network Discovery

# Find and cache devices
devices = escanear_red_tvs()
if devices:
    primary_ip = devices[0]['ip']
    # Use primary_ip for commands

Command Processing

# Parse and execute commands
commands = parse_commands(text)
for cmd in commands:
    if cmd['type'] == 'app':
        launch_app(cmd['value'])

Build docs developers (and LLMs) love