/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.core.voice.internal;
import java.util.HashSet;
import java.util.Locale;
import org.eclipse.smarthome.core.audio.AudioException;
import org.eclipse.smarthome.core.audio.AudioFormat;
import org.eclipse.smarthome.core.audio.AudioSink;
import org.eclipse.smarthome.core.audio.AudioSource;
import org.eclipse.smarthome.core.audio.AudioStream;
import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
import org.eclipse.smarthome.core.voice.KSErrorEvent;
import org.eclipse.smarthome.core.voice.KSEvent;
import org.eclipse.smarthome.core.voice.KSException;
import org.eclipse.smarthome.core.voice.KSListener;
import org.eclipse.smarthome.core.voice.KSService;
import org.eclipse.smarthome.core.voice.KSpottedEvent;
import org.eclipse.smarthome.core.voice.RecognitionStopEvent;
import org.eclipse.smarthome.core.voice.STTEvent;
import org.eclipse.smarthome.core.voice.STTException;
import org.eclipse.smarthome.core.voice.STTListener;
import org.eclipse.smarthome.core.voice.STTService;
import org.eclipse.smarthome.core.voice.STTServiceHandle;
import org.eclipse.smarthome.core.voice.SpeechRecognitionErrorEvent;
import org.eclipse.smarthome.core.voice.SpeechRecognitionEvent;
import org.eclipse.smarthome.core.voice.TTSException;
import org.eclipse.smarthome.core.voice.TTSService;
import org.eclipse.smarthome.core.voice.Voice;
import org.eclipse.smarthome.core.voice.text.HumanLanguageInterpreter;
import org.eclipse.smarthome.core.voice.text.InterpretationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An instance of this class can handle a complete dialog with the user. It orchestrates the keyword spotting, the stt
* and tts services together with the human language interpreter.
*
* @author Kai Kreuzer - Initial contribution and API
*/
public class DialogProcessor implements KSListener, STTListener {
private final Logger logger = LoggerFactory.getLogger(DialogProcessor.class);
/**
* If the processor is currently processing a keyword event and thus should not spot further ones.
*/
private boolean processing = false;
/**
* If the STT server is in the process of aborting
*/
private boolean isSTTServerAborting = false;
private STTServiceHandle sttServiceHandle;
private final KSService ks;
private final STTService stt;
private final TTSService tts;
private final HumanLanguageInterpreter hli;
private final AudioSource source;
private final AudioSink sink;
private final Locale locale;
private final String keyword;
private final AudioFormat format;
public DialogProcessor(KSService ks, STTService stt, TTSService tts, HumanLanguageInterpreter hli,
AudioSource source, AudioSink sink, Locale locale, String keyword) {
this.locale = locale;
this.ks = ks;
this.hli = hli;
this.stt = stt;
this.tts = tts;
this.source = source;
this.sink = sink;
this.keyword = keyword;
this.format = AudioFormat.getBestMatch(source.getSupportedFormats(), sink.getSupportedFormats());
}
public void start() {
try {
ks.spot(this, source.getInputStream(format), locale, this.keyword);
} catch (KSException | AudioException e) {
logger.error("Encountered error calling spot: {}", e.getMessage());
}
}
@Override
public void ksEventReceived(KSEvent ksEvent) {
if (!processing) {
processing = true;
this.isSTTServerAborting = false;
if (ksEvent instanceof KSpottedEvent) {
if (stt != null) {
try {
this.sttServiceHandle = stt.recognize(this, source.getInputStream(format), this.locale,
new HashSet<String>());
} catch (STTException | AudioException e) {
say("Error during recognition: " + e.getMessage());
}
}
} else if (ksEvent instanceof KSErrorEvent) {
KSErrorEvent kse = (KSErrorEvent) ksEvent;
say("Encountered error spotting keywords, " + kse.getMessage());
}
}
}
@Override
public synchronized void sttEventReceived(STTEvent sttEvent) {
if (sttEvent instanceof SpeechRecognitionEvent) {
if (false == this.isSTTServerAborting) {
this.sttServiceHandle.abort();
this.isSTTServerAborting = true;
SpeechRecognitionEvent sre = (SpeechRecognitionEvent) sttEvent;
String question = sre.getTranscript();
try {
this.processing = false;
say(hli.interpret(this.locale, question));
} catch (InterpretationException e) {
say(e.getMessage());
}
}
} else if (sttEvent instanceof RecognitionStopEvent) {
this.processing = false;
} else if (sttEvent instanceof SpeechRecognitionErrorEvent) {
if (false == this.isSTTServerAborting) {
this.sttServiceHandle.abort();
this.isSTTServerAborting = true;
this.processing = false;
SpeechRecognitionErrorEvent sre = (SpeechRecognitionErrorEvent) sttEvent;
say("Encountered error: " + sre.getMessage());
}
}
}
/**
* Says the passed command
*
* @param text The text to say
*/
protected void say(String text) {
try {
Voice voice = null;
for (Voice currentVoice : tts.getAvailableVoices()) {
if (this.locale.getLanguage().equals(currentVoice.getLocale().getLanguage())) {
voice = currentVoice;
break;
}
}
if (null == voice) {
throw new TTSException("Unable to find a suitable voice");
}
AudioStream audioStream = tts.synthesize(text, voice, null);
sink.process(audioStream);
} catch (TTSException | UnsupportedAudioFormatException e) {
logger.error("Error saying '{}'", text);
}
}
}