]> git.basschouten.com Git - openhab-addons.git/commitdiff
[amazonechocontrol] fix unnecessary refresh and add text commands (#9328)
authorJ-N-K <J-N-K@users.noreply.github.com>
Sat, 12 Dec 2020 00:17:20 +0000 (01:17 +0100)
committerGitHub <noreply@github.com>
Sat, 12 Dec 2020 00:17:20 +0000 (16:17 -0800)
* fixed: removed unnecessary refresh on push activity

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
Also-By: Tom Blum <trinitus01@googlemail.com>
Co-authored-by: Tom Blum <trinitus01@googlemail.com>
bundles/org.openhab.binding.amazonechocontrol/README.md
bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java
bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java
bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java
bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java
bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/i18n/amazonechocontrol_de.properties
bundles/org.openhab.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml

index 7837ec06acf85145c4b18a856d98b461182436b3..a9f46ae0cf462ac05ec427804f2cdf9260a9f282 100644 (file)
@@ -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. `<speak>I want to tell you a secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect></speak>`
 | 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
index 7f013bab7bae1bacd5587c575156739096cd82d9..29571574a5c541ea495bfd3771061779447c5614 100644 (file)
@@ -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";
index 587a89fecb2ec91d866333c084e12210a60d6e38..22aba40fb625729ce0592e27b9ddb0722a8bb728 100644 (file)
@@ -162,6 +162,8 @@ public class Connection {
 
     private Map<Integer, AnnouncementWrapper> announcements = Collections.synchronizedMap(new LinkedHashMap<>());
     private Map<Integer, TextToSpeech> textToSpeeches = Collections.synchronizedMap(new LinkedHashMap<>());
+    private Map<Integer, TextCommand> textCommands = Collections.synchronizedMap(new LinkedHashMap<>());
+
     private Map<Integer, Volume> volumes = Collections.synchronizedMap(new LinkedHashMap<>());
     private Map<String, LinkedBlockingQueue<QueueObject>> 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<AnnouncementWrapper> 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<TextToSpeech> iterator = textToSpeeches.values().iterator();
@@ -1424,8 +1429,7 @@ public class Connection {
                     List<Device> devices = textToSpeech.devices;
                     if (!devices.isEmpty()) {
                         String text = textToSpeech.text;
-                        Map<String, Object> parameters = new HashMap<>();
-                        parameters.put("textToSpeak", text);
+                        Map<String, Object> 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<TextCommand> iterator = textCommands.values().iterator();
+            while (iterator.hasNext()) {
+                TextCommand textCommand = iterator.next();
+                try {
+                    List<Device> devices = textCommand.devices;
+                    if (!devices.isEmpty()) {
+                        String text = textCommand.text;
+                        Map<String, Object> 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<Volume> 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<Device> 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<Device> devices = new ArrayList<>();
         public int volume;
index 20a45423cafe40ca13af3d37d407de7b3677b18d..fa774b91138e260202d1fc7563baa1059c1fa73f 100644 (file)
@@ -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":
index 5e4d26ead4311b4cf97fe6402af9e1aa8fc7a657..3fc7fec488adee228b446056607c10935bf9e23a 100644 (file)
@@ -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));
index 1661f411aa4103599e2110a6134567a4ee45e5d3..ebb0fc87d2f9d53609c4635582029a551d1f921b 100644 (file)
@@ -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.
 
index 13f81cf1c51bcba58e1400503dd28b99c554d421..421a502548e0cf10ac6cd1e8bb4f68f2bab13d25 100644 (file)
@@ -73,6 +73,7 @@
                        <channel id="announcement" typeId="announcement"/>
                        <channel id="textToSpeech" typeId="textToSpeech"/>
                        <channel id="textToSpeechVolume" typeId="textToSpeechVolume"/>
+                       <channel id="textCommand" typeId="textCommand"/>
                        <channel id="remind" typeId="remind"/>
                        <channel id="nextReminder" typeId="nextReminder"/>
                        <channel id="playAlarmSound" typeId="playAlarmSound"/>
                        <channel id="announcement" typeId="announcement"/>
                        <channel id="textToSpeech" typeId="textToSpeech"/>
                        <channel id="textToSpeechVolume" typeId="textToSpeechVolume"/>
+                       <channel id="textCommand" typeId="textCommand"/>
                        <channel id="remind" typeId="remind"/>
                        <channel id="nextReminder" typeId="nextReminder"/>
                        <channel id="playAlarmSound" typeId="playAlarmSound"/>
                        <channel id="announcement" typeId="announcement"/>
                        <channel id="textToSpeech" typeId="textToSpeech"/>
                        <channel id="textToSpeechVolume" typeId="textToSpeechVolume"/>
+                       <channel id="textCommand" typeId="textCommand"/>
                        <channel id="remind" typeId="remind"/>
                        <channel id="nextReminder" typeId="nextReminder"/>
                        <channel id="playAlarmSound" typeId="playAlarmSound"/>
                <label>Speak Volume</label>
                <description>Volume of the Speak channel. If 0, the current volume will be used.</description>
        </channel-type>
+       <channel-type id="textCommand" advanced="true">
+               <item-type>String</item-type>
+               <label>TextCommand</label>
+               <description>Run a command (Write only). The command can run like a spoken command.</description>
+       </channel-type>
        <channel-type id="lastVoiceCommand" advanced="true">
                <item-type>String</item-type>
                <label>Last Voice Command</label>