diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java index 7e792309c..bfa9c2a5b 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/VoiceManager.java @@ -334,7 +334,7 @@ public interface VoiceManager { * @return a TTS service or null, if no service with this id exists */ @Nullable - TTSService getTTS(String id); + TTSService getTTS(@Nullable String id); /** * Retrieves all TTS services. @@ -361,7 +361,7 @@ public interface VoiceManager { * @return a STT service or null, if no service with this id exists */ @Nullable - STTService getSTT(String id); + STTService getSTT(@Nullable String id); /** * Retrieves all STT services. @@ -388,7 +388,7 @@ public interface VoiceManager { * @return a KS service or null, if no service with this id exists */ @Nullable - KSService getKS(String id); + KSService getKS(@Nullable String id); /** * Retrieves all KS services. @@ -404,7 +404,7 @@ public interface VoiceManager { * @param ids Comma separated list of HLI service ids to use * @return a List or empty, if none of the services is available */ - List getHLIsByIds(String ids); + List getHLIsByIds(@Nullable String ids); /** * Retrieves a HumanLanguageInterpreter collection. @@ -433,7 +433,7 @@ public interface VoiceManager { * @return a HumanLanguageInterpreter or null, if no interpreter with this id exists */ @Nullable - HumanLanguageInterpreter getHLI(String id); + HumanLanguageInterpreter getHLI(@Nullable String id); /** * Retrieves all HumanLanguageInterpreters. diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java index 0a2923f40..c29b0091b 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceConsoleCommandExtension.java @@ -16,6 +16,7 @@ import static java.util.Comparator.comparing; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -23,8 +24,6 @@ import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.audio.AudioManager; -import org.openhab.core.audio.AudioSink; -import org.openhab.core.audio.AudioSource; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.io.console.Console; import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension; @@ -33,6 +32,7 @@ import org.openhab.core.items.Item; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemNotUniqueException; import org.openhab.core.items.ItemRegistry; +import org.openhab.core.voice.DialogContext; import org.openhab.core.voice.KSService; import org.openhab.core.voice.STTService; import org.openhab.core.voice.TTSService; @@ -87,14 +87,13 @@ public class VoiceConsoleCommandExtension extends AbstractConsoleCommandExtensio return List.of(buildCommandUsage(SUBCMD_SAY + " ", "speaks a text"), buildCommandUsage(SUBCMD_INTERPRET + " ", "interprets a human language command"), buildCommandUsage(SUBCMD_VOICES, "lists available voices of the TTS services"), - buildCommandUsage( - SUBCMD_START_DIALOG - + " [ [ [ [ [ [ { []]]]]]]]", + buildCommandUsage(SUBCMD_START_DIALOG + + " [--source ] [--sink ] [--interpreters ] [--tts [--voice ]] [--stt ] [--ks ks [--keyword ]] [--listening-item ]", "start a new dialog processing using the default services or the services identified with provided arguments"), buildCommandUsage(SUBCMD_STOP_DIALOG + " []", "stop the dialog processing for the default audio source or the audio source identified with provided argument"), - buildCommandUsage( - SUBCMD_LISTEN_ANSWER + " [ [ [ [ [ [] [--sink ] [--interpreters ] [--tts [--voice ]] [--stt ] [--listening-item ]", "Execute a simple dialog sequence without keyword spotting using the default services or the services identified with provided arguments"), buildCommandUsage(SUBCMD_INTERPRETERS, "lists the interpreters"), buildCommandUsage(SUBCMD_KEYWORD_SPOTTERS, "lists the keyword spotters"), @@ -107,21 +106,23 @@ public class VoiceConsoleCommandExtension extends AbstractConsoleCommandExtensio if (args.length > 0) { String subCommand = args[0]; switch (subCommand) { - case SUBCMD_SAY: + case SUBCMD_SAY -> { if (args.length > 1) { say(Arrays.copyOfRange(args, 1, args.length), console); } else { console.println("Specify text to say (e.g. 'say hello')"); } return; - case SUBCMD_INTERPRET: + } + case SUBCMD_INTERPRET -> { if (args.length > 1) { interpret(Arrays.copyOfRange(args, 1, args.length), console); } else { console.println("Specify text to interpret (e.g. 'interpret turn all lights off')"); } return; - case SUBCMD_VOICES: + } + case SUBCMD_VOICES -> { Locale locale = localeProvider.getLocale(); Voice defaultVoice = getDefaultVoice(); for (Voice voice : voiceManager.getAllVoices()) { @@ -133,64 +134,71 @@ public class VoiceConsoleCommandExtension extends AbstractConsoleCommandExtensio } } return; - case SUBCMD_START_DIALOG: + } + case SUBCMD_START_DIALOG -> { + DialogContext.Builder dialogContextBuilder; try { - AudioSource source = args.length < 2 ? null : audioManager.getSource(args[1]); - AudioSink sink = args.length < 3 ? null : audioManager.getSink(args[2]); - List hlis = args.length < 4 ? List.of() - : voiceManager.getHLIsByIds(args[3]); - TTSService tts = args.length < 5 ? null : voiceManager.getTTS(args[4]); - STTService stt = args.length < 6 ? null : voiceManager.getSTT(args[5]); - KSService ks = args.length < 7 ? null : voiceManager.getKS(args[6]); - Voice voice = args.length < 8 ? null : getVoice(args[7]); - String keyword = args.length < 9 ? null : args[8]; - voiceManager.startDialog(ks, stt, tts, voice, hlis, source, sink, null, keyword, null); + dialogContextBuilder = parseDialogParameters(args); + } catch (IllegalStateException e) { + console.println(Objects.requireNonNullElse(e.getMessage(), + "An error occurred while parsing the dialog options")); + break; + } + try { + voiceManager.startDialog(dialogContextBuilder.build()); } catch (IllegalStateException e) { console.println(Objects.requireNonNullElse(e.getMessage(), "An error occurred while starting the dialog")); } - break; - case SUBCMD_STOP_DIALOG: + return; + } + case SUBCMD_STOP_DIALOG -> { try { voiceManager.stopDialog(args.length < 2 ? null : audioManager.getSource(args[1])); } catch (IllegalStateException e) { console.println(Objects.requireNonNullElse(e.getMessage(), "An error occurred while stopping the dialog")); } - break; - case SUBCMD_LISTEN_ANSWER: + return; + } + case SUBCMD_LISTEN_ANSWER -> { + DialogContext.Builder dialogContextBuilder; try { - AudioSource source = args.length < 2 ? null : audioManager.getSource(args[1]); - AudioSink sink = args.length < 3 ? null : audioManager.getSink(args[2]); - List hlis = args.length < 4 ? List.of() - : voiceManager.getHLIsByIds(args[3]); - TTSService tts = args.length < 5 ? null : voiceManager.getTTS(args[4]); - STTService stt = args.length < 6 ? null : voiceManager.getSTT(args[5]); - Voice voice = args.length < 7 ? null : getVoice(args[6]); - voiceManager.listenAndAnswer(stt, tts, voice, hlis, source, sink, null, null); + dialogContextBuilder = parseDialogParameters(args); + } catch (IllegalStateException e) { + console.println(Objects.requireNonNullElse(e.getMessage(), + "An error occurred while parsing the dialog options")); + break; + } + try { + voiceManager.listenAndAnswer(dialogContextBuilder.build()); } catch (IllegalStateException e) { console.println(Objects.requireNonNullElse(e.getMessage(), "An error occurred while executing the simple dialog sequence")); } - break; - case SUBCMD_INTERPRETERS: + return; + } + case SUBCMD_INTERPRETERS -> { listInterpreters(console); return; - case SUBCMD_KEYWORD_SPOTTERS: + } + case SUBCMD_KEYWORD_SPOTTERS -> { listKeywordSpotters(console); return; - case SUBCMD_STT_SERVICES: + } + case SUBCMD_STT_SERVICES -> { listSTTs(console); return; - case SUBCMD_TTS_SERVICES: + } + case SUBCMD_TTS_SERVICES -> { listTTSs(console); return; - default: - break; + } + default -> { + } } - } else { - printUsage(console); } + printUsage(console); } private @Nullable Voice getDefaultVoice() { @@ -213,9 +221,7 @@ public class VoiceConsoleCommandExtension extends AbstractConsoleCommandExtensio String msg = sb.toString(); try { String result = voiceManager.interpret(msg); - if (result != null) { - console.println(result); - } + console.println(result); } catch (InterpretationException ie) { console.println(Objects.requireNonNullElse(ie.getMessage(), String.format("An error occurred while interpreting '%s'", msg))); @@ -229,7 +235,7 @@ public class VoiceConsoleCommandExtension extends AbstractConsoleCommandExtensio String itemName = word.substring(1, word.length() - 1); try { Item item = this.itemRegistry.getItemByPattern(itemName); - msg.append(item.getState().toString()); + msg.append(item.getState()); } catch (ItemNotFoundException e) { console.println("Error: Item '" + itemName + "' does not exist."); } catch (ItemNotUniqueException e) { @@ -302,7 +308,59 @@ public class VoiceConsoleCommandExtension extends AbstractConsoleCommandExtensio } } - private @Nullable Voice getVoice(String id) { - return voiceManager.getAllVoices().stream().filter(voice -> voice.getUID().equals(id)).findAny().orElse(null); + private @Nullable Voice getVoice(@Nullable String id) { + return id == null ? null + : voiceManager.getAllVoices().stream().filter(voice -> voice.getUID().equals(id)).findAny() + .orElse(null); + } + + private DialogContext.Builder parseDialogParameters(String[] args) { + var dialogContextBuilder = voiceManager.getDialogContextBuilder(); + if (args.length < 2) { + return dialogContextBuilder; + } + var parameters = new HashMap(); + for (int i = 1; i < args.length; i++) { + var arg = args[i].trim(); + if (arg.startsWith("--")) { + i++; + if (i < args.length) { + parameters.put(arg.replace("--", ""), args[i].trim()); + } else { + throw new IllegalStateException("Missing value for argument " + arg); + } + } else { + throw new IllegalStateException("Argument name should start by -- " + arg); + } + } + String sourceId = parameters.remove("source"); + if (sourceId != null) { + var source = audioManager.getSource(sourceId); + if (source == null) { + throw new IllegalStateException("Audio source not found"); + } + dialogContextBuilder.withSource(source); + } + String sinkId = parameters.remove("sink"); + if (sinkId != null) { + var sink = audioManager.getSink(sinkId); + if (sink == null) { + throw new IllegalStateException("Audio sink not found"); + } + dialogContextBuilder.withSink(sink); + } + dialogContextBuilder // + .withSTT(voiceManager.getSTT(parameters.remove("stt"))) // + .withTTS(voiceManager.getTTS(parameters.remove("tts"))) // + .withVoice(getVoice(parameters.remove("voice"))) // + .withHLIs(voiceManager.getHLIsByIds(parameters.remove("hlis"))) // + .withKS(voiceManager.getKS(parameters.remove("ks"))) // + .withListeningItem(parameters.remove("listening-item")) // + .withKeyword(parameters.remove("keyword")); + if (!parameters.isEmpty()) { + throw new IllegalStateException( + "Argument" + parameters.keySet().stream().findAny().orElse("") + "is not supported"); + } + return dialogContextBuilder; } } diff --git a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java index 311f19704..a3c1796ec 100644 --- a/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java +++ b/bundles/org.openhab.core.voice/src/main/java/org/openhab/core/voice/internal/VoiceManagerImpl.java @@ -941,8 +941,8 @@ public class VoiceManagerImpl implements VoiceManager, ConfigOptionProvider, Dia } @Override - public List getHLIsByIds(String ids) { - return getHLIsByIds(Arrays.asList(ids.split(","))); + public List getHLIsByIds(@Nullable String ids) { + return ids == null ? List.of() : getHLIsByIds(Arrays.asList(ids.split(","))); } @Override @@ -1074,7 +1074,7 @@ public class VoiceManagerImpl implements VoiceManager, ConfigOptionProvider, Dia */ private void buildDialogRegistrations() { synchronized (dialogRegistrationStorage) { - dialogRegistrationStorage.getValues().stream().forEach(dr -> { + dialogRegistrationStorage.getValues().forEach(dr -> { if (dr != null && !dialogProcessors.containsKey(dr.sourceId)) { try { startDialog(getDialogContextBuilder() //