diff --git a/bundles/org.openhab.binding.amazonechocontrol/README.md b/bundles/org.openhab.binding.amazonechocontrol/README.md
index 7837ec06acf..a9f46ae0cf4 100644
--- a/bundles/org.openhab.binding.amazonechocontrol/README.md
+++ b/bundles/org.openhab.binding.amazonechocontrol/README.md
@@ -5,6 +5,7 @@ This binding can control Amazon Echo devices (Alexa).
It provides features to control and view the current state of echo devices:
- use echo device as text to speech from a rule
+- execute a text command
- volume
- pause/continue/next track/previous track
- connect/disconnect bluetooth devices
@@ -179,6 +180,7 @@ It will be configured at runtime by using the save channel to store the current
| announcement | String | W | echo, echoshow, echospot | Write Only! Display the announcement message on the display. See in the tutorial section to learn how it’s possible to set the title and turn off the sound.
| textToSpeech | String | W | echo, echoshow, echospot | Write Only! Write some text to this channel and Alexa will speak it. It is possible to use plain text or SSML: e.g. `I want to tell you a secret.I am not a real human.`
| textToSpeechVolume | Dimmer | R/W | echo, echoshow, echospot | Volume of the textToSpeech channel, if 0 the current volume will be used
+| textCommand | String | W | echo, echoshow, echospot | Write Only! Execute a text command (like a spoken text)
| lastVoiceCommand | String | R/W | echo, echoshow, echospot | Last voice command spoken to the device. Writing to the channel starts voice output.
| mediaProgress | Dimmer | R/W | echo, echoshow, echospot | Media progress in percent
| mediaProgressTime | Number:Time | R/W | echo, echoshow, echospot | Media play time
@@ -199,7 +201,6 @@ E.g. to read out the history call from an installation on openhab:8080 with an a
http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1
-
### Example
#### echo.things
diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java
index 7f013bab7ba..29571574a5c 100644
--- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java
+++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java
@@ -76,6 +76,7 @@ public class AmazonEchoControlBindingConstants {
public static final String CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID = "amazonMusicPlayListId";
public static final String CHANNEL_TEXT_TO_SPEECH = "textToSpeech";
public static final String CHANNEL_TEXT_TO_SPEECH_VOLUME = "textToSpeechVolume";
+ public static final String CHANNEL_TEXT_COMMAND = "textCommand";
public static final String CHANNEL_REMIND = "remind";
public static final String CHANNEL_PLAY_ALARM_SOUND = "playAlarmSound";
public static final String CHANNEL_START_ROUTINE = "startRoutine";
diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java
index 587a89fecb2..22aba40fb62 100644
--- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java
+++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java
@@ -162,6 +162,8 @@ public class Connection {
private Map announcements = Collections.synchronizedMap(new LinkedHashMap<>());
private Map textToSpeeches = Collections.synchronizedMap(new LinkedHashMap<>());
+ private Map textCommands = Collections.synchronizedMap(new LinkedHashMap<>());
+
private Map volumes = Collections.synchronizedMap(new LinkedHashMap<>());
private Map> devices = Collections.synchronizedMap(new LinkedHashMap<>());
@@ -172,7 +174,8 @@ public class Connection {
ANNOUNCEMENT,
TTS,
VOLUME,
- DEVICES
+ DEVICES,
+ TEXT_COMMAND
}
public Connection(@Nullable Connection oldConnection, Gson gson) {
@@ -962,6 +965,8 @@ public class Connection {
replaceTimer(TimerType.VOLUME, null);
volumes.clear();
replaceTimer(TimerType.DEVICES, null);
+ textCommands.clear();
+ replaceTimer(TimerType.TTS, null);
devices.values().forEach((queueObjects) -> {
queueObjects.forEach((queueObject) -> {
@@ -1354,7 +1359,7 @@ public class Connection {
private void sendAnnouncement() {
// we lock new announcements until we have dispatched everything
- Lock lock = locks.computeIfAbsent(TimerType.ANNOUNCEMENT, k -> new ReentrantLock());
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.ANNOUNCEMENT, k -> new ReentrantLock()));
lock.lock();
try {
Iterator iterator = announcements.values().iterator();
@@ -1396,7 +1401,7 @@ public class Connection {
}
// we lock TTS until we have finished adding this one
- Lock lock = locks.computeIfAbsent(TimerType.TTS, k -> new ReentrantLock());
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.TTS, k -> new ReentrantLock()));
lock.lock();
try {
TextToSpeech textToSpeech = Objects
@@ -1414,7 +1419,7 @@ public class Connection {
private void sendTextToSpeech() {
// we lock new TTS until we have dispatched everything
- Lock lock = locks.computeIfAbsent(TimerType.TTS, k -> new ReentrantLock());
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.TTS, k -> new ReentrantLock()));
lock.lock();
try {
Iterator iterator = textToSpeeches.values().iterator();
@@ -1424,8 +1429,7 @@ public class Connection {
List devices = textToSpeech.devices;
if (!devices.isEmpty()) {
String text = textToSpeech.text;
- Map parameters = new HashMap<>();
- parameters.put("textToSpeak", text);
+ Map parameters = Map.of("textToSpeak", text);
executeSequenceCommandWithVolume(devices, "Alexa.Speak", parameters, textToSpeech.ttsVolumes,
textToSpeech.standardVolumes);
}
@@ -1441,9 +1445,60 @@ public class Connection {
}
}
+ public void textCommand(Device device, String text, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) {
+ if (text.replaceAll("<.+?>", "").replaceAll("\\s+", " ").trim().isEmpty()) {
+ return;
+ }
+
+ // we lock TextCommands until we have finished adding this one
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.TEXT_COMMAND, k -> new ReentrantLock()));
+ lock.lock();
+ try {
+ TextCommand textCommand = Objects
+ .requireNonNull(textCommands.computeIfAbsent(Objects.hash(text), k -> new TextCommand(text)));
+ textCommand.devices.add(device);
+ textCommand.ttsVolumes.add(ttsVolume);
+ textCommand.standardVolumes.add(standardVolume);
+ // schedule a TextCommand only if it has not been scheduled before
+ timers.computeIfAbsent(TimerType.TEXT_COMMAND,
+ k -> scheduler.schedule(this::sendTextCommand, 500, TimeUnit.MILLISECONDS));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private synchronized void sendTextCommand() {
+ // we lock new TTS until we have dispatched everything
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.TEXT_COMMAND, k -> new ReentrantLock()));
+ lock.lock();
+
+ try {
+ Iterator iterator = textCommands.values().iterator();
+ while (iterator.hasNext()) {
+ TextCommand textCommand = iterator.next();
+ try {
+ List devices = textCommand.devices;
+ if (!devices.isEmpty()) {
+ String text = textCommand.text;
+ Map parameters = Map.of("text", text);
+ executeSequenceCommandWithVolume(devices, "Alexa.TextCommand", parameters,
+ textCommand.ttsVolumes, textCommand.standardVolumes);
+ }
+ } catch (Exception e) {
+ logger.warn("send textCommand fails with unexpected error", e);
+ }
+ iterator.remove();
+ }
+ } finally {
+ // the timer is done anyway immediately after we unlock
+ timers.remove(TimerType.TEXT_COMMAND);
+ lock.unlock();
+ }
+ }
+
public void volume(Device device, int vol) {
// we lock volume until we have finished adding this one
- Lock lock = locks.computeIfAbsent(TimerType.VOLUME, k -> new ReentrantLock());
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.VOLUME, k -> new ReentrantLock()));
lock.lock();
try {
Volume volume = Objects.requireNonNull(volumes.computeIfAbsent(vol, k -> new Volume(vol)));
@@ -1459,7 +1514,7 @@ public class Connection {
private void sendVolume() {
// we lock new volume until we have dispatched everything
- Lock lock = locks.computeIfAbsent(TimerType.VOLUME, k -> new ReentrantLock());
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.VOLUME, k -> new ReentrantLock()));
lock.lock();
try {
Iterator iterator = volumes.values().iterator();
@@ -1504,7 +1559,7 @@ public class Connection {
if (command != null && !parameters.isEmpty()) {
JsonArray commandNodesToExecute = new JsonArray();
- if ("Alexa.Speak".equals(command)) {
+ if ("Alexa.Speak".equals(command) || "Alexa.TextCommand".equals(command)) {
for (Device device : devices) {
commandNodesToExecute.add(createExecutionNode(device, command, parameters));
}
@@ -1565,7 +1620,7 @@ public class Connection {
}
private void handleExecuteSequenceNode() {
- Lock lock = locks.computeIfAbsent(TimerType.DEVICES, k -> new ReentrantLock());
+ Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.DEVICES, k -> new ReentrantLock()));
if (lock.tryLock()) {
try {
for (String serialNumber : devices.keySet()) {
@@ -1707,6 +1762,9 @@ public class Connection {
JsonObject nodeToExecute = new JsonObject();
nodeToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode");
nodeToExecute.addProperty("type", command);
+ if ("Alexa.TextCommand".equals(command)) {
+ nodeToExecute.addProperty("skillId", "amzn1.ask.1p.tellalexa");
+ }
nodeToExecute.add("operationPayload", operationPayload);
return nodeToExecute;
}
@@ -2047,6 +2105,17 @@ public class Connection {
}
}
+ private static class TextCommand {
+ public List devices = new ArrayList<>();
+ public String text;
+ public List<@Nullable Integer> ttsVolumes = new ArrayList<>();
+ public List<@Nullable Integer> standardVolumes = new ArrayList<>();
+
+ public TextCommand(String text) {
+ this.text = text;
+ }
+ }
+
private static class Volume {
public List devices = new ArrayList<>();
public int volume;
diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java
index 20a45423caf..fa774b91138 100644
--- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java
+++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java
@@ -743,11 +743,6 @@ public class AccountHandler extends BaseBridgeHandler implements IWebSocketComma
switch (command) {
case "PUSH_ACTIVITY":
handlePushActivity(pushCommand.payload);
- if (refreshDataDelayed != null) {
- refreshDataDelayed.cancel(false);
- }
- this.refreshAfterCommandJob = scheduler.schedule(this::refreshAfterCommand, 700,
- TimeUnit.MILLISECONDS);
break;
case "PUSH_DOPPLER_CONNECTION_CHANGE":
case "PUSH_BLUETOOTH_STATE_CHANGE":
diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java
index 5e4d26ead43..3fc7fec488a 100644
--- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java
+++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java
@@ -114,6 +114,7 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
private boolean disableUpdate = false;
private boolean updateRemind = true;
private boolean updateTextToSpeech = true;
+ private boolean updateTextCommand = true;
private boolean updateAlarm = true;
private boolean updateRoutine = true;
private boolean updatePlayMusicVoiceCommand = true;
@@ -589,6 +590,16 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
}
this.updateState(channelId, new PercentType(textToSpeechVolume));
}
+ if (channelId.equals(CHANNEL_TEXT_COMMAND)) {
+ if (command instanceof StringType) {
+ String text = command.toFullString();
+ if (!text.isEmpty()) {
+ waitForUpdate = 1000;
+ updateTextCommand = true;
+ startTextCommand(connection, device, text);
+ }
+ }
+ }
if (channelId.equals(CHANNEL_LAST_VOICE_COMMAND)) {
if (command instanceof StringType) {
String text = command.toFullString();
@@ -713,6 +724,15 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
connection.textToSpeech(device, text, volume, lastKnownVolume);
}
+ private void startTextCommand(Connection connection, Device device, String text)
+ throws IOException, URISyntaxException {
+ Integer volume = null;
+ if (textToSpeechVolume != 0) {
+ volume = textToSpeechVolume;
+ }
+ connection.textCommand(device, text, volume, lastKnownVolume);
+ }
+
@Override
public void startAnnouncement(Device device, String speak, String bodyText, @Nullable String title,
@Nullable Integer volume) throws IOException, URISyntaxException {
@@ -770,11 +790,11 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
String type = currentNotification.type;
if (type != null) {
if (type.equals("Reminder")) {
- updateState(CHANNEL_REMIND, new StringType(""));
+ updateState(CHANNEL_REMIND, StringType.EMPTY);
updateRemind = false;
}
if (type.equals("Alarm")) {
- updateState(CHANNEL_PLAY_ALARM_SOUND, new StringType(""));
+ updateState(CHANNEL_PLAY_ALARM_SOUND, StringType.EMPTY);
updateAlarm = false;
}
}
@@ -919,7 +939,7 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
}
} catch (HttpException e) {
if (e.getCode() == 400) {
- updateState(CHANNEL_RADIO_STATION_ID, new StringType(""));
+ updateState(CHANNEL_RADIO_STATION_ID, StringType.EMPTY);
} else {
logger.info("getMediaState fails", e);
}
@@ -1069,27 +1089,31 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
// Update states
if (updateRemind && currentNotifcationUpdateTimer == null) {
updateRemind = false;
- updateState(CHANNEL_REMIND, new StringType(""));
+ updateState(CHANNEL_REMIND, StringType.EMPTY);
}
if (updateAlarm && currentNotifcationUpdateTimer == null) {
updateAlarm = false;
- updateState(CHANNEL_PLAY_ALARM_SOUND, new StringType(""));
+ updateState(CHANNEL_PLAY_ALARM_SOUND, StringType.EMPTY);
}
if (updateRoutine) {
updateRoutine = false;
- updateState(CHANNEL_START_ROUTINE, new StringType(""));
+ updateState(CHANNEL_START_ROUTINE, StringType.EMPTY);
}
if (updateTextToSpeech) {
updateTextToSpeech = false;
- updateState(CHANNEL_TEXT_TO_SPEECH, new StringType(""));
+ updateState(CHANNEL_TEXT_TO_SPEECH, StringType.EMPTY);
+ }
+ if (updateTextCommand) {
+ updateTextCommand = false;
+ updateState(CHANNEL_TEXT_COMMAND, StringType.EMPTY);
}
if (updatePlayMusicVoiceCommand) {
updatePlayMusicVoiceCommand = false;
- updateState(CHANNEL_PLAY_MUSIC_VOICE_COMMAND, new StringType(""));
+ updateState(CHANNEL_PLAY_MUSIC_VOICE_COMMAND, StringType.EMPTY);
}
if (updateStartCommand) {
updateStartCommand = false;
- updateState(CHANNEL_START_COMMAND, new StringType(""));
+ updateState(CHANNEL_START_COMMAND, StringType.EMPTY);
}
updateState(CHANNEL_MUSIC_PROVIDER_ID, new StringType(musicProviderId));
@@ -1225,7 +1249,7 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
}
if (lastSpokenText.isEmpty() || lastSpokenText.equals(spokenText)) {
- updateState(CHANNEL_LAST_VOICE_COMMAND, new StringType(""));
+ updateState(CHANNEL_LAST_VOICE_COMMAND, StringType.EMPTY);
}
lastSpokenText = spokenText;
updateState(CHANNEL_LAST_VOICE_COMMAND, new StringType(spokenText));
diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/i18n/amazonechocontrol_de.properties b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/i18n/amazonechocontrol_de.properties
index 1661f411aa4..ebb0fc87d2f 100644
--- a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/i18n/amazonechocontrol_de.properties
+++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/i18n/amazonechocontrol_de.properties
@@ -88,6 +88,9 @@ channel-type.amazonechocontrol.textToSpeech.description = Spricht den Text (Nur
channel-type.amazonechocontrol.textToSpeechVolume.label = Sprich Lautstärke
channel-type.amazonechocontrol.textToSpeechVolume.description = Lautstärke des Sprich Kanals. Wenn 0 wird die aktuelle Lautstärke verwendet.
+channel-type.amazonechocontrol.textCommand.label = Befehl
+channel-type.amazonechocontrol.textCommand.description = Führt einen Befehl aus (Nur schreiben). Der Befehl wird wie ein gesprochener Befehl ausgeführt.
+
channel-type.amazonechocontrol.lastVoiceCommand.label = Letzter Sprachbefehl
channel-type.amazonechocontrol.lastVoiceCommand.description = Befehl der zum Gerät gesprochen wurde. Schreiben zum Kanal started die Sprachausgabe.
diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml
index 13f81cf1c51..421a502548e 100644
--- a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml
@@ -73,6 +73,7 @@
+
@@ -127,6 +128,7 @@
+
@@ -181,6 +183,7 @@
+
@@ -452,6 +455,11 @@
Volume of the Speak channel. If 0, the current volume will be used.
+
+ String
+
+ Run a command (Write only). The command can run like a spoken command.
+ String