Skip to main content
Glama
wakeWordDetector.ts4.77 kB
import { IWakeWordDetector } from "./types.js"; import { logger } from "../utils/logger.js"; import { spawn } from "child_process"; import { EventEmitter } from "events"; export class WakeWordDetector extends EventEmitter implements IWakeWordDetector { private isListening: boolean = false; private isInitialized: boolean = false; private wyomingHost: string; private wyomingPort: number; constructor(host: string = "localhost", port: number = 10400) { super(); this.wyomingHost = host; this.wyomingPort = port; } public async initialize(): Promise<void> { if (this.isInitialized) { return; } try { await this.setupDetector(); this.isInitialized = true; logger.info("Wake word detector initialized successfully"); } catch (error) { logger.error("Failed to initialize wake word detector:", error); throw error; } } public async shutdown(): Promise<void> { if (this.isListening) { await this.stopListening(); } if (this.isInitialized) { await this.cleanupDetector(); this.isInitialized = false; logger.info("Wake word detector shut down"); } } public async startListening(): Promise<void> { if (!this.isInitialized) { throw new Error("Wake word detector is not initialized"); } if (this.isListening) { return; } try { await this.startDetection(); this.isListening = true; logger.info("Wake word detection started"); } catch (error) { logger.error("Failed to start wake word detection:", error); throw error; } } public async stopListening(): Promise<void> { if (!this.isListening) { return; } try { await this.stopDetection(); this.isListening = false; logger.info("Wake word detection stopped"); } catch (error) { logger.error("Failed to stop wake word detection:", error); throw error; } } private async setupDetector(): Promise<void> { try { // Test connection to Wyoming openwakeword service const response = await fetch( `http://${this.wyomingHost}:${this.wyomingPort}/status`, { timeout: 5000 } ); if (!response.ok) { throw new Error( `Wyoming wake word service responded with ${response.status}` ); } logger.info( `Connected to Wyoming openwakeword at ${this.wyomingHost}:${this.wyomingPort}` ); } catch (error) { logger.warn("Wyoming service not available, continuing anyway:", error); // Don't fail initialization if Wyoming is not available yet } } private async cleanupDetector(): Promise<void> { // Cleanup any resources if needed } private startDetection(): Promise<void> { return new Promise((resolve, reject) => { try { // Start ffmpeg to capture audio and stream to Wyoming service const ffmpeg = spawn("ffmpeg", [ "-f", "alsa", "-i", "default", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", "-f", "s16le", "pipe:1", ]); ffmpeg.stdout?.on("data", (chunk: Buffer) => { void this.processAudioChunk(chunk); }); ffmpeg.stderr?.on("data", (data) => { logger.debug(`FFmpeg: ${data}`); }); ffmpeg.on("error", (error) => { logger.error("FFmpeg error:", error); reject(error); }); logger.info("Audio capture started for wake word detection"); resolve(); } catch (error) { reject(error); } }); } private stopDetection(): Promise<void> { return new Promise((resolve) => { // Kill ffmpeg process if running try { spawn("killall", ["ffmpeg"]); setTimeout(resolve, 100); } catch { resolve(); } }); } private async processAudioChunk(chunk: Buffer): Promise<void> { try { // Send audio chunk to Wyoming service via TCP const net = await import("net"); const socket = net.createConnection(this.wyomingPort, this.wyomingHost); socket.on("connect", () => { socket.write(chunk); socket.end(); }); socket.on("data", (data: Buffer) => { const response = data.toString(); if (response.includes("wake_word")) { logger.info("Wake word detected!"); this.emit("wake_word_detected", { timestamp: new Date() }); } }); socket.on("error", (error) => { logger.error("Socket error:", error); }); } catch (error) { logger.error("Error processing audio chunk:", error); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/advanced-homeassistant-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server