Ports prior voice assistant research and prototypes from devl/Devops into the Minerva repo. Includes: - docs/: architecture, wake word guides, ESP32-S3 spec, hardware buying guide - scripts/: voice_server.py, voice_server_enhanced.py, setup scripts - hardware/maixduino/: edge device scripts with WiFi credentials scrubbed (replaced hardcoded password with secrets.py pattern) - config/.env.example: server config template - .gitignore: excludes .env, secrets.py, model blobs, ELF firmware - CLAUDE.md: Minerva product context and connection to cf-voice roadmap
252 lines
7.4 KiB
Python
252 lines
7.4 KiB
Python
# Maix Duino - Simple Test Script
|
|
# Copy/paste this into MaixPy IDE and click RUN
|
|
#
|
|
# This script tests:
|
|
# 1. LCD display
|
|
# 2. WiFi connectivity
|
|
# 3. Network connection to Heimdall server
|
|
# 4. I2S audio initialization (without recording yet)
|
|
|
|
import time
|
|
import lcd
|
|
from Maix import GPIO, I2S
|
|
from fpioa_manager import fm
|
|
|
|
# Import the correct network module
|
|
try:
|
|
import network
|
|
# Create ESP32_SPI instance (for Maix Duino with ESP32)
|
|
nic = None # Will be initialized in test_wifi
|
|
except Exception as e:
|
|
print("Network module import error: " + str(e))
|
|
nic = None
|
|
|
|
# ===== CONFIGURATION - EDIT THESE =====
|
|
# Load credentials from secrets.py (gitignored)
|
|
try:
|
|
from secrets import SECRETS
|
|
except ImportError:
|
|
SECRETS = {}
|
|
|
|
WIFI_SSID = "Tell My WiFi Love Her" # <<< CHANGE THIS
|
|
WIFI_PASSWORD = SECRETS.get("wifi_password", "") # set in secrets.py # <<< CHANGE THIS
|
|
SERVER_URL = "http://10.1.10.71:3006" # Heimdall voice server
|
|
# =======================================
|
|
|
|
# Colors (as tuples for easy reference)
|
|
COLOR_BLACK = (0, 0, 0)
|
|
COLOR_WHITE = (255, 255, 255)
|
|
COLOR_RED = (255, 0, 0)
|
|
COLOR_GREEN = (0, 255, 0)
|
|
COLOR_BLUE = (0, 0, 255)
|
|
COLOR_YELLOW = (255, 255, 0)
|
|
|
|
def display_msg(msg, color=COLOR_WHITE, y=50):
|
|
"""Display message on LCD"""
|
|
# lcd.draw_string needs RGB as separate ints: lcd.draw_string(x, y, text, color_int, bg_color_int)
|
|
# Convert RGB tuple to single integer: (R << 16) | (G << 8) | B
|
|
color_int = (color[0] << 16) | (color[1] << 8) | color[2]
|
|
bg_int = 0 # Black background
|
|
lcd.draw_string(10, y, msg, color_int, bg_int)
|
|
print(msg)
|
|
|
|
def test_lcd():
|
|
"""Test LCD display"""
|
|
lcd.init()
|
|
lcd.clear(COLOR_BLACK)
|
|
display_msg("MaixDuino Test", COLOR_YELLOW, 10)
|
|
display_msg("Initializing...", COLOR_WHITE, 30)
|
|
time.sleep(1)
|
|
return True
|
|
|
|
def test_wifi():
|
|
"""Test WiFi connection"""
|
|
global nic
|
|
display_msg("Connecting WiFi...", COLOR_BLUE, 50)
|
|
|
|
try:
|
|
# Initialize ESP32_SPI network interface
|
|
print("Initializing ESP32_SPI...")
|
|
|
|
# Create network interface instance with Maix Duino pins
|
|
# Maix Duino ESP32 default pins:
|
|
# CS=25, RST=8, RDY=9, MOSI=28, MISO=26, SCLK=27
|
|
from network import ESP32_SPI
|
|
from fpioa_manager import fm
|
|
from Maix import GPIO
|
|
|
|
# Register pins for ESP32 SPI communication
|
|
fm.register(25, fm.fpioa.GPIOHS10, force=True) # CS
|
|
fm.register(8, fm.fpioa.GPIOHS11, force=True) # RST
|
|
fm.register(9, fm.fpioa.GPIOHS12, force=True) # RDY
|
|
fm.register(28, fm.fpioa.GPIOHS13, force=True) # MOSI
|
|
fm.register(26, fm.fpioa.GPIOHS14, force=True) # MISO
|
|
fm.register(27, fm.fpioa.GPIOHS15, force=True) # SCLK
|
|
|
|
nic = ESP32_SPI(
|
|
cs=fm.fpioa.GPIOHS10,
|
|
rst=fm.fpioa.GPIOHS11,
|
|
rdy=fm.fpioa.GPIOHS12,
|
|
mosi=fm.fpioa.GPIOHS13,
|
|
miso=fm.fpioa.GPIOHS14,
|
|
sclk=fm.fpioa.GPIOHS15
|
|
)
|
|
|
|
print("Connecting to " + WIFI_SSID + "...")
|
|
|
|
# Connect to WiFi (no need to call active() first)
|
|
nic.connect(WIFI_SSID, WIFI_PASSWORD)
|
|
|
|
# Wait for connection
|
|
timeout = 20
|
|
while timeout > 0:
|
|
time.sleep(1)
|
|
timeout -= 1
|
|
|
|
if nic.isconnected():
|
|
# Successfully connected!
|
|
ip_info = nic.ifconfig()
|
|
ip = ip_info[0] if ip_info else "Unknown"
|
|
display_msg("WiFi OK!", COLOR_GREEN, 70)
|
|
display_msg("IP: " + str(ip), COLOR_WHITE, 90)
|
|
print("Connected! IP: " + str(ip))
|
|
time.sleep(2)
|
|
return True
|
|
else:
|
|
print("Waiting... " + str(timeout) + "s")
|
|
|
|
# Timeout reached
|
|
display_msg("WiFi FAILED!", COLOR_RED, 70)
|
|
print("Connection timeout")
|
|
return False
|
|
|
|
except Exception as e:
|
|
display_msg("WiFi error!", COLOR_RED, 70)
|
|
print("WiFi error: " + str(e))
|
|
import sys
|
|
sys.print_exception(e)
|
|
return False
|
|
|
|
def test_server():
|
|
"""Test connection to Heimdall server"""
|
|
display_msg("Testing server...", COLOR_BLUE, 110)
|
|
|
|
try:
|
|
# Try socket connection to server
|
|
import socket
|
|
|
|
url = SERVER_URL + "/health"
|
|
print("Trying: " + url)
|
|
|
|
# Parse URL to get host and port
|
|
host = "10.1.10.71"
|
|
port = 3006
|
|
|
|
# Create socket
|
|
s = socket.socket()
|
|
s.settimeout(5)
|
|
|
|
print("Connecting to " + host + ":" + str(port))
|
|
s.connect((host, port))
|
|
|
|
# Send HTTP GET request
|
|
request = "GET /health HTTP/1.1\r\nHost: " + host + "\r\nConnection: close\r\n\r\n"
|
|
s.send(request.encode())
|
|
|
|
# Read response
|
|
response = s.recv(1024).decode()
|
|
s.close()
|
|
|
|
print("Server response received")
|
|
|
|
if "200" in response or "OK" in response:
|
|
display_msg("Server OK!", COLOR_GREEN, 130)
|
|
print("Server is reachable!")
|
|
time.sleep(2)
|
|
return True
|
|
else:
|
|
display_msg("Server responded", COLOR_YELLOW, 130)
|
|
print("Response: " + response[:100])
|
|
return True # Still counts as success if we got a response
|
|
|
|
except Exception as e:
|
|
display_msg("Server FAILED!", COLOR_RED, 130)
|
|
error_msg = str(e)[:30]
|
|
display_msg(error_msg, COLOR_RED, 150)
|
|
print("Server connection failed: " + str(e))
|
|
return False
|
|
|
|
def test_audio():
|
|
"""Test I2S audio initialization"""
|
|
display_msg("Testing audio...", COLOR_BLUE, 170)
|
|
|
|
try:
|
|
# Register I2S pins (Maix Duino pinout)
|
|
fm.register(20, fm.fpioa.I2S0_IN_D0, force=True)
|
|
fm.register(19, fm.fpioa.I2S0_WS, force=True)
|
|
fm.register(18, fm.fpioa.I2S0_SCLK, force=True)
|
|
|
|
# Initialize I2S
|
|
rx = I2S(I2S.DEVICE_0)
|
|
rx.channel_config(rx.CHANNEL_0, rx.RECEIVER, align_mode=I2S.STANDARD_MODE)
|
|
rx.set_sample_rate(16000)
|
|
|
|
display_msg("Audio OK!", COLOR_GREEN, 190)
|
|
print("I2S initialized: " + str(rx))
|
|
time.sleep(2)
|
|
return True
|
|
except Exception as e:
|
|
display_msg("Audio FAILED!", COLOR_RED, 190)
|
|
print("Audio init failed: " + str(e))
|
|
return False
|
|
|
|
def main():
|
|
"""Run all tests"""
|
|
print("=" * 40)
|
|
print("MaixDuino Voice Assistant Test")
|
|
print("=" * 40)
|
|
|
|
# Test LCD
|
|
if not test_lcd():
|
|
print("LCD test failed!")
|
|
return
|
|
|
|
# Test WiFi
|
|
if not test_wifi():
|
|
print("WiFi test failed!")
|
|
red_int = (255 << 16) | (0 << 8) | 0 # Red color
|
|
lcd.draw_string(10, 210, "STOPPED - Check WiFi", red_int, 0)
|
|
return
|
|
|
|
# Test server connection
|
|
server_ok = test_server()
|
|
|
|
# Test audio
|
|
audio_ok = test_audio()
|
|
|
|
# Summary
|
|
lcd.clear(COLOR_BLACK)
|
|
display_msg("=== TEST RESULTS ===", COLOR_YELLOW, 10)
|
|
display_msg("LCD: OK", COLOR_GREEN, 40)
|
|
display_msg("WiFi: OK", COLOR_GREEN, 60)
|
|
|
|
if server_ok:
|
|
display_msg("Server: OK", COLOR_GREEN, 80)
|
|
else:
|
|
display_msg("Server: FAIL", COLOR_RED, 80)
|
|
|
|
if audio_ok:
|
|
display_msg("Audio: OK", COLOR_GREEN, 100)
|
|
else:
|
|
display_msg("Audio: FAIL", COLOR_RED, 100)
|
|
|
|
if server_ok and audio_ok:
|
|
display_msg("Ready for voice app!", COLOR_GREEN, 140)
|
|
else:
|
|
display_msg("Fix errors first", COLOR_YELLOW, 140)
|
|
|
|
print("\nTest complete!")
|
|
|
|
# Run the test
|
|
if __name__ == "__main__":
|
|
main()
|