hey jarvis banger
This commit is contained in:
parent
9ee09afa77
commit
c3b7e018fb
8
.claude/settings.local.json
Normal file
8
.claude/settings.local.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(wc -l /Users/arthurbarre/dev/perso/ti-pote/apps/robot-client/src/**/*.ts)",
|
||||||
|
"Bash(ssh tipote@192.168.1.124 \"ps aux | grep node | grep -v grep; echo '---LOGS---'; cat /tmp/tipote.log\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -121,8 +121,20 @@ export class RobotGateway implements OnGatewayConnection, OnGatewayDisconnect, I
|
|||||||
@ConnectedSocket() client: AuthenticatedSocket,
|
@ConnectedSocket() client: AuthenticatedSocket,
|
||||||
@MessageBody() message: AudioChunkMessage,
|
@MessageBody() message: AudioChunkMessage,
|
||||||
) {
|
) {
|
||||||
this.conversationPort.processAudioChunk(client.data.deviceId, message.data, message.sampleRate);
|
const chunk = message.data;
|
||||||
|
// Debug: log audio chunk info to diagnose STT issues
|
||||||
|
if (!this._audioLogCount) this._audioLogCount = 0;
|
||||||
|
this._audioLogCount++;
|
||||||
|
if (this._audioLogCount <= 3 || this._audioLogCount % 100 === 0) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Audio chunk #${this._audioLogCount}: type=${typeof chunk}, isBuffer=${Buffer.isBuffer(chunk)}, ` +
|
||||||
|
`constructor=${chunk?.constructor?.name}, length=${chunk?.length ?? chunk?.byteLength ?? 'N/A'}, ` +
|
||||||
|
`sampleRate=${message.sampleRate}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.conversationPort.processAudioChunk(client.data.deviceId, chunk, message.sampleRate);
|
||||||
}
|
}
|
||||||
|
private _audioLogCount = 0;
|
||||||
|
|
||||||
@SubscribeMessage('speech_end')
|
@SubscribeMessage('speech_end')
|
||||||
async handleSpeechEnd(@ConnectedSocket() client: AuthenticatedSocket) {
|
async handleSpeechEnd(@ConnectedSocket() client: AuthenticatedSocket) {
|
||||||
|
|||||||
@ -97,7 +97,7 @@ export class ConversationService implements IConversationPort {
|
|||||||
this.activeSessions.set(deviceId, session);
|
this.activeSessions.set(deviceId, session);
|
||||||
|
|
||||||
const sttStream = await this.sttPort.openStream((result: TranscriptionResult) => {
|
const sttStream = await this.sttPort.openStream((result: TranscriptionResult) => {
|
||||||
this.logger.debug(
|
this.logger.log(
|
||||||
`STT [${deviceId}]: "${result.text}" (final: ${result.isFinal}, confidence: ${result.confidence})`,
|
`STT [${deviceId}]: "${result.text}" (final: ${result.isFinal}, confidence: ${result.confidence})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -156,6 +156,9 @@ export class ConversationService implements IConversationPort {
|
|||||||
this.logger.log(`Final transcription for ${deviceId}: "${finalText}"`);
|
this.logger.log(`Final transcription for ${deviceId}: "${finalText}"`);
|
||||||
|
|
||||||
if (!finalText) {
|
if (!finalText) {
|
||||||
|
this.logger.warn(`No transcription for ${deviceId} — returning to idle`);
|
||||||
|
this.deviceGateway.sendStatus(deviceId, 'idle');
|
||||||
|
this.activeSessions.delete(deviceId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -147,11 +147,15 @@ def run_predict_loop(oww_model, read_chunk, state: State, threshold: float):
|
|||||||
# Keep draining but don't emit detections.
|
# Keep draining but don't emit detections.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for _, score in oww_model.prediction_buffer.items():
|
for name, score in oww_model.prediction_buffer.items():
|
||||||
if len(score) > 0 and score[-1] > threshold:
|
if len(score) > 0:
|
||||||
print("DETECTED", flush=True)
|
s = score[-1]
|
||||||
oww_model.reset()
|
if s > 0.05:
|
||||||
break
|
print(f"SCORE: {name}={s:.3f}", file=sys.stderr, flush=True)
|
||||||
|
if s > threshold:
|
||||||
|
print("DETECTED", flush=True)
|
||||||
|
oww_model.reset()
|
||||||
|
break
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export class OrchestratorService extends EventEmitter {
|
|||||||
/** Timer for Voice Activity Detection (silence timeout) */
|
/** Timer for Voice Activity Detection (silence timeout) */
|
||||||
private silenceTimer: ReturnType<typeof setTimeout> | null = null;
|
private silenceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
private readonly silenceTimeoutMs = 2000; // 2s of silence = speech end
|
private readonly silenceTimeoutMs = 2000; // 2s of silence = speech end
|
||||||
private readonly initialGracePeriodMs = 3000; // 3s grace period before silence detection kicks in
|
private readonly initialGracePeriodMs = 5000; // 5s grace period before silence detection kicks in
|
||||||
|
|
||||||
/** Track when the last audio chunk was received */
|
/** Track when the last audio chunk was received */
|
||||||
private lastAudioChunkTime = 0;
|
private lastAudioChunkTime = 0;
|
||||||
@ -243,6 +243,14 @@ export class OrchestratorService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.audioBuffer = [];
|
this.audioBuffer = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset speech detection: the wake word phrase ("Hey Jarvis") itself
|
||||||
|
// triggers hasDetectedSpeech=true, and the natural pause after saying it
|
||||||
|
// fires the 2s silence timeout before the user can ask their question.
|
||||||
|
// Restart the clock from NOW so the user gets the full grace period.
|
||||||
|
this.hasDetectedSpeech = false;
|
||||||
|
this.lastAudioChunkTime = Date.now();
|
||||||
|
this.conversationStartTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The cloud drives the state machine for thinking → speaking → idle
|
// The cloud drives the state machine for thinking → speaking → idle
|
||||||
@ -269,8 +277,10 @@ export class OrchestratorService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After playback, return to idle and wait for a new wake word
|
// After playback, keep the conversation going — listen for follow-up
|
||||||
this.returnToIdle();
|
// without requiring a new wake word. The conversation ends naturally
|
||||||
|
// when silence exceeds the grace period (no speech detected).
|
||||||
|
this.continueListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -37,8 +37,9 @@ export class WifiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync('nmcli -t -f TYPE,STATE device | grep wifi');
|
const { stdout } = await execAsync('nmcli -t -f TYPE,STATE device');
|
||||||
return stdout.includes('connected') && !stdout.includes('disconnected');
|
// Check that the main wifi device (not wifi-p2p) is connected
|
||||||
|
return stdout.split('\n').some((line) => line === 'wifi:connected');
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user