import java.io.IOException;
import java.io.InputStream;
+import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.CookieManager;
import java.net.CookieStore;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection;
-import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonActivities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonSyntaxException;
+import com.google.gson.*;
/**
* The {@link Connection} is responsible for the connection to the amazon server
private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)");
private static final String DEVICE_TYPE = "A2IVLV5VM2W81";
- protected final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(THING_THREADPOOL_NAME);
-
private final Logger logger = LoggerFactory.getLogger(Connection.class);
+ protected final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(THING_THREADPOOL_NAME);
+
private final Random rand = new Random();
private final CookieManager cookieManager = new CookieManager();
+ private final Gson gson;
+ private final Gson gsonWithNullSerialization;
+
private String amazonSite = "amazon.com";
private String alexaServer = "https://alexa.amazon.com";
private final String userAgent;
private @Nullable String accountCustomerId;
private @Nullable String customerName;
- private Map<Integer, Announcement> announcements = new LinkedHashMap<>();
- private Map<Integer, TextToSpeech> textToSpeeches = new LinkedHashMap<>();
- private Map<Integer, Volume> volumes = new LinkedHashMap<>();
- private @Nullable ScheduledFuture<?> announcementTimer;
- private @Nullable ScheduledFuture<?> textToSpeechTimer;
- private @Nullable ScheduledFuture<?> volumeTimer;
+ private Map<Integer, Announcement> announcements = Collections.synchronizedMap(new LinkedHashMap<>());
+ private Map<Integer, TextToSpeech> textToSpeeches = Collections.synchronizedMap(new LinkedHashMap<>());
+ private Map<Integer, Volume> volumes = Collections.synchronizedMap(new LinkedHashMap<>());
+ private Map<String, LinkedBlockingQueue<QueueObject>> devices = Collections.synchronizedMap(new LinkedHashMap<>());
- private final Gson gson;
- private final Gson gsonWithNullSerialization;
+ private final Map<TimerType, ScheduledFuture<?>> timers = new ConcurrentHashMap<>();
+ private final Map<TimerType, Lock> locks = new ConcurrentHashMap<>();
- private Map<Device, QueueObject> singles = Collections.synchronizedMap(new LinkedHashMap<>());
- private Map<Device, QueueObject> groups = Collections.synchronizedMap(new LinkedHashMap<>());
- public @Nullable ScheduledFuture<?> singleGroupTimer;
+ private enum TimerType {
+ ANNOUNCEMENT,
+ TTS,
+ VOLUME,
+ DEVICES
+ }
public Connection(@Nullable Connection oldConnection, Gson gson) {
this.gson = gson;
// build user agent
this.userAgent = "AmazonWebView/Amazon Alexa/2.2.223830.0/iOS/11.4.1/iPhone";
-
- // setAmazonSite(amazonSite);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonWithNullSerialization = gsonBuilder.create();
+
+ replaceTimer(TimerType.DEVICES,
+ scheduler.scheduleWithFixedDelay(this::handleExecuteSequenceNode, 0, 500, TimeUnit.MILLISECONDS));
}
/**
* Generate a new device id
- *
+ * <p>
* The device id consists of 16 random bytes in upper-case hex format, a # as separator and a fixed DEVICE_TYPE
*
* @return a string containing the new device-id
}
public boolean isSequenceNodeQueueRunning() {
- return singles.values().stream().anyMatch(queueObject -> queueObject.queueRunning.get())
- || groups.values().stream().anyMatch(queueObject -> queueObject.queueRunning.get());
+ return devices.values().stream().anyMatch(
+ (queueObjects) -> (queueObjects.stream().anyMatch(queueObject -> queueObject.future != null)));
}
public String serializeLoginData() {
}
} catch (IOException e) {
return false;
- } catch (URISyntaxException e) {
+ } catch (URISyntaxException | InterruptedException e) {
}
}
return false;
String accountCustomerId = this.accountCustomerId;
if (accountCustomerId == null || accountCustomerId.isEmpty()) {
List<Device> devices = this.getDeviceList();
- for (Device device : devices) {
- final String serial = this.serial;
- if (serial != null && serial.equals(device.serialNumber)) {
- this.accountCustomerId = device.deviceOwnerCustomerId;
- break;
- }
- }
- accountCustomerId = this.accountCustomerId;
+ accountCustomerId = devices.stream().filter(device -> serial.equals(device.serialNumber)).findAny()
+ .map(device -> device.deviceOwnerCustomerId).orElse(null);
if (accountCustomerId == null || accountCustomerId.isEmpty()) {
- for (Device device : devices) {
- if ("This Device".equals(device.accountName)) {
- this.accountCustomerId = device.deviceOwnerCustomerId;
- String serial = device.serialNumber;
- if (serial != null) {
- this.serial = serial;
- }
- break;
- }
- }
+ accountCustomerId = devices.stream().filter(device -> "This Device".equals(device.accountName))
+ .findAny().map(device -> {
+ serial = Objects.requireNonNullElse(device.serialNumber, serial);
+ return device.deviceOwnerCustomerId;
+ }).orElse(null);
}
+ this.accountCustomerId = accountCustomerId;
}
- } catch (URISyntaxException | IOException | ConnectionException e) {
+ } catch (URISyntaxException | IOException | InterruptedException | ConnectionException e) {
logger.debug("Getting account customer Id failed", e);
}
return loginTime;
}
- private @Nullable Authentication tryGetBootstrap() throws IOException, URISyntaxException {
+ private @Nullable Authentication tryGetBootstrap() throws IOException, URISyntaxException, InterruptedException {
HttpsURLConnection connection = makeRequest("GET", alexaServer + "/api/bootstrap", null, false, false, null, 0);
String contentType = connection.getContentType();
if (connection.getResponseCode() == 200 && contentType != null
return result;
}
- public String makeRequestAndReturnString(String url) throws IOException, URISyntaxException {
+ public String makeRequestAndReturnString(String url) throws IOException, URISyntaxException, InterruptedException {
return makeRequestAndReturnString("GET", url, null, false, null);
}
public String makeRequestAndReturnString(String verb, String url, @Nullable String postData, boolean json,
- @Nullable Map<String, String> customHeaders) throws IOException, URISyntaxException {
+ @Nullable Map<String, String> customHeaders) throws IOException, URISyntaxException, InterruptedException {
HttpsURLConnection connection = makeRequest(verb, url, postData, json, true, customHeaders, 3);
String result = convertStream(connection);
logger.debug("Result of {} {}:{}", verb, url, result);
public HttpsURLConnection makeRequest(String verb, String url, @Nullable String postData, boolean json,
boolean autoredirect, @Nullable Map<String, String> customHeaders, int badRequestRepeats)
- throws IOException, URISyntaxException {
+ throws IOException, URISyntaxException, InterruptedException {
String currentUrl = url;
int redirectCounter = 0;
int retryCounter = 0;
String location = null;
// handle response headers
- Map<String, List<String>> headerFields = connection.getHeaderFields();
- for (Map.Entry<String, List<String>> header : headerFields.entrySet()) {
+ Map<@Nullable String, List<String>> headerFields = connection.getHeaderFields();
+ for (Map.Entry<@Nullable String, List<String>> header : headerFields.entrySet()) {
String key = header.getKey();
if (key != null && !key.isEmpty()) {
if (key.equalsIgnoreCase("Set-Cookie")) {
// store cookie
for (String cookieHeader : header.getValue()) {
- if (cookieHeader != null && !cookieHeader.isEmpty()) {
+ if (!cookieHeader.isEmpty()) {
List<HttpCookie> cookies = HttpCookie.parse(cookieHeader);
for (HttpCookie cookie : cookies) {
cookieManager.getCookieStore().add(uri, cookie);
if (key.equalsIgnoreCase("Location")) {
// get redirect location
location = header.getValue().get(0);
- if (location != null && !location.isEmpty()) {
+ if (!location.isEmpty()) {
location = uri.resolve(location).toString();
// check for https
if (location.toLowerCase().startsWith("http://")) {
throw new HttpException(code,
verb + " url '" + url + "' failed: " + connection.getResponseMessage());
}
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- logger.warn("Unable to wait for next call to {}", url, e);
- }
+ Thread.sleep(2000);
}
+ } catch (InterruptedException | InterruptedIOException e) {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ logger.warn("Unable to wait for next call to {}", url, e);
+ throw e;
} catch (IOException e) {
if (connection != null) {
connection.disconnect();
}
public String registerConnectionAsApp(String oAutRedirectUrl)
- throws ConnectionException, IOException, URISyntaxException {
+ throws ConnectionException, IOException, URISyntaxException, InterruptedException {
URI oAutRedirectUri = new URI(oAutRedirectUrl);
Map<String, String> queryParameters = new LinkedHashMap<>();
JsonRegisterAppResponse registerAppResponse = parseJson(registerAppResultJson, JsonRegisterAppResponse.class);
if (registerAppResponse == null) {
- throw new ConnectionException("Error: No response receivec from register application");
+ throw new ConnectionException("Error: No response received from register application");
}
Response response = registerAppResponse.response;
if (response == null) {
if (bearer == null) {
throw new ConnectionException("Error: No bearer received from register application");
}
- this.refreshToken = bearer.refreshToken;
- if (this.refreshToken == null || this.refreshToken.isEmpty()) {
+ String refreshToken = bearer.refreshToken;
+ this.refreshToken = refreshToken;
+ if (refreshToken == null || refreshToken.isEmpty()) {
throw new ConnectionException("Error: No refresh token received");
}
try {
return deviceName;
}
- private void exchangeToken() throws IOException, URISyntaxException {
+ private void exchangeToken() throws IOException, URISyntaxException, InterruptedException {
this.renewTime = 0;
String cookiesJson = "{\"cookies\":{\"." + getAmazonSite() + "\":[]}}";
String cookiesBase64 = Base64.getEncoder().encodeToString(cookiesJson.getBytes());
String exchangeTokenJson = makeRequestAndReturnString("POST",
"https://www." + getAmazonSite() + "/ap/exchangetoken", exchangePostData, false, exchangeTokenHeader);
- JsonExchangeTokenResponse exchangeTokenResponse = gson.fromJson(exchangeTokenJson,
- JsonExchangeTokenResponse.class);
+ JsonExchangeTokenResponse exchangeTokenResponse = Objects
+ .requireNonNull(gson.fromJson(exchangeTokenJson, JsonExchangeTokenResponse.class));
org.openhab.binding.amazonechocontrol.internal.jsons.JsonExchangeTokenResponse.Response response = exchangeTokenResponse.response;
if (response != null) {
if (cookiesMap != null) {
for (String domain : cookiesMap.keySet()) {
Cookie[] cookies = cookiesMap.get(domain);
- for (Cookie cookie : cookies) {
- if (cookie != null) {
- HttpCookie httpCookie = new HttpCookie(cookie.name, cookie.value);
- httpCookie.setPath(cookie.path);
- httpCookie.setDomain(domain);
- Boolean secure = cookie.secure;
- if (secure != null) {
- httpCookie.setSecure(secure);
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if (cookie != null) {
+ HttpCookie httpCookie = new HttpCookie(cookie.name, cookie.value);
+ httpCookie.setPath(cookie.path);
+ httpCookie.setDomain(domain);
+ Boolean secure = cookie.secure;
+ if (secure != null) {
+ httpCookie.setSecure(secure);
+ }
+ this.cookieManager.getCookieStore().add(null, httpCookie);
}
- this.cookieManager.getCookieStore().add(null, httpCookie);
}
}
}
this.renewTime = (long) (System.currentTimeMillis() + Connection.EXPIRES_IN * 1000d / 0.8d); // start renew at
}
- public boolean checkRenewSession() throws URISyntaxException, IOException {
+ public boolean checkRenewSession() throws URISyntaxException, IOException, InterruptedException {
if (System.currentTimeMillis() >= this.renewTime) {
String renewTokenPostData = "app_name=Amazon%20Alexa&app_version=2.2.223830.0&di.sdk.version=6.10.0&source_token="
+ URLEncoder.encode(refreshToken, StandardCharsets.UTF_8.name())
return loginTime != null;
}
- public String getLoginPage() throws IOException, URISyntaxException {
+ public String getLoginPage() throws IOException, URISyntaxException, InterruptedException {
// clear session data
logout();
return loginFormHtml;
}
- public boolean verifyLogin() throws IOException, URISyntaxException {
+ public boolean verifyLogin() throws IOException, URISyntaxException, InterruptedException {
if (this.refreshToken == null) {
return false;
}
}
}
+ private void replaceTimer(TimerType type, @Nullable ScheduledFuture<?> newTimer) {
+ timers.compute(type, (timerType, oldTimer) -> {
+ if (oldTimer != null) {
+ oldTimer.cancel(true);
+ }
+ return newTimer;
+ });
+ }
+
public void logout() {
cookieManager.getCookieStore().removeAll();
// reset all members
verifyTime = null;
deviceName = null;
- if (announcementTimer != null) {
- announcements.clear();
- announcementTimer.cancel(true);
- }
- if (textToSpeechTimer != null) {
- textToSpeeches.clear();
- textToSpeechTimer.cancel(true);
- }
- if (volumeTimer != null) {
- volumes.clear();
- volumeTimer.cancel(true);
- }
- singles.values().forEach(queueObject -> queueObject.dispose());
- groups.values().forEach(queueObject -> queueObject.dispose());
- if (singleGroupTimer != null) {
- singleGroupTimer.cancel(true);
- }
+ replaceTimer(TimerType.ANNOUNCEMENT, null);
+ announcements.clear();
+ replaceTimer(TimerType.TTS, null);
+ textToSpeeches.clear();
+ replaceTimer(TimerType.VOLUME, null);
+ volumes.clear();
+ replaceTimer(TimerType.DEVICES, null);
+
+ devices.values().forEach((queueObjects) -> {
+ queueObjects.forEach((queueObject) -> {
+ Future<?> future = queueObject.future;
+ if (future != null) {
+ future.cancel(true);
+ queueObject.future = null;
+ }
+ });
+ });
}
// parser
try {
return gson.fromJson(json, type);
} catch (JsonParseException | IllegalStateException e) {
- logger.warn("Parsing json failed", e);
- logger.warn("Illegal json: {}", json);
+ logger.warn("Parsing json failed: {}", json, e);
throw e;
}
}
return result;
}
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.info("getting wakewords failed", e);
}
return new WakeWord[0];
}
- public List<SmartHomeBaseDevice> getSmarthomeDeviceList() throws IOException, URISyntaxException {
+ public List<SmartHomeBaseDevice> getSmarthomeDeviceList()
+ throws IOException, URISyntaxException, InterruptedException {
try {
String json = makeRequestAndReturnString(alexaServer + "/api/phoenix");
logger.debug("getSmartHomeDevices result: {}", json);
private void searchSmartHomeDevicesRecursive(@Nullable Object jsonNode, List<SmartHomeBaseDevice> devices) {
if (jsonNode instanceof Map) {
@SuppressWarnings("rawtypes")
- Map map = (Map) jsonNode;
+ Map<String, Object> map = (Map) jsonNode;
if (map.containsKey("entityId") && map.containsKey("friendlyName") && map.containsKey("actions")) {
// device node found, create type element and add it to the results
JsonElement element = gson.toJsonTree(jsonNode);
}
}
- public List<Device> getDeviceList() throws IOException, URISyntaxException {
+ public List<Device> getDeviceList() throws IOException, URISyntaxException, InterruptedException {
String json = getDeviceListJson();
JsonDevices devices = parseJson(json, JsonDevices.class);
if (devices != null) {
return Collections.emptyList();
}
- public String getDeviceListJson() throws IOException, URISyntaxException {
+ public String getDeviceListJson() throws IOException, URISyntaxException, InterruptedException {
String json = makeRequestAndReturnString(alexaServer + "/api/devices-v2/device?cached=false");
return json;
}
public Map<String, JsonArray> getSmartHomeDeviceStatesJson(Set<String> applianceIds)
- throws IOException, URISyntaxException {
+ throws IOException, URISyntaxException, InterruptedException {
JsonObject requestObject = new JsonObject();
JsonArray stateRequests = new JsonArray();
for (String applianceId : applianceIds) {
String json = makeRequestAndReturnString("POST", alexaServer + "/api/phoenix/state", requestBody, true, null);
logger.trace("Requested {} and received {}", requestBody, json);
- JsonObject responseObject = this.gson.fromJson(json, JsonObject.class);
+ JsonObject responseObject = Objects.requireNonNull(gson.fromJson(json, JsonObject.class));
JsonArray deviceStates = (JsonArray) responseObject.get("deviceStates");
Map<String, JsonArray> result = new HashMap<>();
for (JsonElement deviceState : deviceStates) {
return result;
}
- public @Nullable JsonPlayerState getPlayer(Device device) throws IOException, URISyntaxException {
+ public @Nullable JsonPlayerState getPlayer(Device device)
+ throws IOException, URISyntaxException, InterruptedException {
String json = makeRequestAndReturnString(alexaServer + "/api/np/player?deviceSerialNumber="
+ device.serialNumber + "&deviceType=" + device.deviceType + "&screenWidth=1440");
JsonPlayerState playerState = parseJson(json, JsonPlayerState.class);
return playerState;
}
- public @Nullable JsonMediaState getMediaState(Device device) throws IOException, URISyntaxException {
+ public @Nullable JsonMediaState getMediaState(Device device)
+ throws IOException, URISyntaxException, InterruptedException {
String json = makeRequestAndReturnString(alexaServer + "/api/media/state?deviceSerialNumber="
+ device.serialNumber + "&deviceType=" + device.deviceType);
JsonMediaState mediaState = parseJson(json, JsonMediaState.class);
return activiesArray;
}
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.info("getting activities failed", e);
}
return new Activity[0];
String json;
try {
json = makeRequestAndReturnString(alexaServer + "/api/bluetooth?cached=true");
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.debug("failed to get bluetooth state: {}", e.getMessage());
return new JsonBluetoothStates();
}
return bluetoothStates;
}
- public @Nullable JsonPlaylists getPlaylists(Device device) throws IOException, URISyntaxException {
- String json = makeRequestAndReturnString(alexaServer + "/api/cloudplayer/playlists?deviceSerialNumber="
- + device.serialNumber + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId="
- + (this.accountCustomerId == null || this.accountCustomerId.isEmpty() ? device.deviceOwnerCustomerId
- : this.accountCustomerId));
+ public @Nullable JsonPlaylists getPlaylists(Device device)
+ throws IOException, URISyntaxException, InterruptedException {
+ String json = makeRequestAndReturnString(
+ alexaServer + "/api/cloudplayer/playlists?deviceSerialNumber=" + device.serialNumber + "&deviceType="
+ + device.deviceType + "&mediaOwnerCustomerId=" + getCustomerId(device.deviceOwnerCustomerId));
JsonPlaylists playlists = parseJson(json, JsonPlaylists.class);
return playlists;
}
- public void command(Device device, String command) throws IOException, URISyntaxException {
+ public void command(Device device, String command) throws IOException, URISyntaxException, InterruptedException {
String url = alexaServer + "/api/np/command?deviceSerialNumber=" + device.serialNumber + "&deviceType="
+ device.deviceType;
makeRequest("POST", url, command, true, true, null, 0);
}
- public void smartHomeCommand(String entityId, String action) throws IOException {
+ public void smartHomeCommand(String entityId, String action) throws IOException, InterruptedException {
smartHomeCommand(entityId, action, null, null);
}
public void smartHomeCommand(String entityId, String action, @Nullable String property, @Nullable Object value)
- throws IOException {
+ throws IOException, InterruptedException {
String url = alexaServer + "/api/phoenix/state";
JsonObject json = new JsonObject();
}
}
- public void notificationVolume(Device device, int volume) throws IOException, URISyntaxException {
+ public void notificationVolume(Device device, int volume)
+ throws IOException, URISyntaxException, InterruptedException {
String url = alexaServer + "/api/device-notification-state/" + device.deviceType + "/" + device.softwareVersion
+ "/" + device.serialNumber;
String command = "{\"deviceSerialNumber\":\"" + device.serialNumber + "\",\"deviceType\":\"" + device.deviceType
makeRequest("PUT", url, command, true, true, null, 0);
}
- public void ascendingAlarm(Device device, boolean ascendingAlarm) throws IOException, URISyntaxException {
+ public void ascendingAlarm(Device device, boolean ascendingAlarm)
+ throws IOException, URISyntaxException, InterruptedException {
String url = alexaServer + "/api/ascending-alarm/" + device.serialNumber;
String command = "{\"ascendingAlarmEnabled\":" + (ascendingAlarm ? "true" : "false")
+ ",\"deviceSerialNumber\":\"" + device.serialNumber + "\",\"deviceType\":\"" + device.deviceType
return deviceNotificationStates;
}
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.info("Error getting device notification states", e);
}
return new DeviceNotificationState[0];
return ascendingAlarmModelList;
}
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.info("Error getting device notification states", e);
}
return new AscendingAlarmModel[0];
}
- public void bluetooth(Device device, @Nullable String address) throws IOException, URISyntaxException {
+ public void bluetooth(Device device, @Nullable String address)
+ throws IOException, URISyntaxException, InterruptedException {
if (address == null || address.isEmpty()) {
// disconnect
makeRequest("POST",
}
}
- public void playRadio(Device device, @Nullable String stationId) throws IOException, URISyntaxException {
+ private @Nullable String getCustomerId(@Nullable String defaultId) {
+ String accountCustomerId = this.accountCustomerId;
+ return accountCustomerId == null || accountCustomerId.isEmpty() ? defaultId : accountCustomerId;
+ }
+
+ public void playRadio(Device device, @Nullable String stationId)
+ throws IOException, URISyntaxException, InterruptedException {
if (stationId == null || stationId.isEmpty()) {
command(device, "{\"type\":\"PauseCommand\"}");
} else {
alexaServer + "/api/tunein/queue-and-play?deviceSerialNumber=" + device.serialNumber
+ "&deviceType=" + device.deviceType + "&guideId=" + stationId
+ "&contentType=station&callSign=&mediaOwnerCustomerId="
- + (this.accountCustomerId == null || this.accountCustomerId.isEmpty()
- ? device.deviceOwnerCustomerId
- : this.accountCustomerId),
+ + getCustomerId(device.deviceOwnerCustomerId),
"", true, true, null, 0);
}
}
- public void playAmazonMusicTrack(Device device, @Nullable String trackId) throws IOException, URISyntaxException {
+ public void playAmazonMusicTrack(Device device, @Nullable String trackId)
+ throws IOException, URISyntaxException, InterruptedException {
if (trackId == null || trackId.isEmpty()) {
command(device, "{\"type\":\"PauseCommand\"}");
} else {
makeRequest("POST",
alexaServer + "/api/cloudplayer/queue-and-play?deviceSerialNumber=" + device.serialNumber
+ "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId="
- + (this.accountCustomerId == null || this.accountCustomerId.isEmpty()
- ? device.deviceOwnerCustomerId
- : this.accountCustomerId)
- + "&shuffle=false",
+ + getCustomerId(device.deviceOwnerCustomerId) + "&shuffle=false",
command, true, true, null, 0);
}
}
public void playAmazonMusicPlayList(Device device, @Nullable String playListId)
- throws IOException, URISyntaxException {
+ throws IOException, URISyntaxException, InterruptedException {
if (playListId == null || playListId.isEmpty()) {
command(device, "{\"type\":\"PauseCommand\"}");
} else {
makeRequest("POST",
alexaServer + "/api/cloudplayer/queue-and-play?deviceSerialNumber=" + device.serialNumber
+ "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId="
- + (this.accountCustomerId == null || this.accountCustomerId.isEmpty()
- ? device.deviceOwnerCustomerId
- : this.accountCustomerId)
- + "&shuffle=false",
+ + getCustomerId(device.deviceOwnerCustomerId) + "&shuffle=false",
command, true, true, null, 0);
}
}
- public void sendNotificationToMobileApp(String customerId, String text, @Nullable String title)
- throws IOException, URISyntaxException {
- Map<String, Object> parameters = new HashMap<>();
- parameters.put("notificationMessage", text);
- parameters.put("alexaUrl", "#v2/behaviors");
- if (title != null && !title.isEmpty()) {
- parameters.put("title", title);
- } else {
- parameters.put("title", "OpenHAB");
- }
- parameters.put("customerId", customerId);
- executeSequenceCommand(null, "Alexa.Notifications.SendMobilePush", parameters);
- }
-
- public synchronized void announcement(Device device, String speak, String bodyText, @Nullable String title,
+ public void announcement(Device device, String speak, String bodyText, @Nullable String title,
@Nullable Integer ttsVolume, @Nullable Integer standardVolume) {
- if (speak == null || speak.replaceAll("<.+?>", " ").replaceAll("\\s+", " ").trim().isEmpty()) {
+ if (speak.replaceAll("<.+?>", " ").replaceAll("\\s+", " ").trim().isEmpty()) {
return;
}
- if (announcementTimer != null) {
- announcementTimer.cancel(true);
- announcementTimer = null;
+
+ // we lock announcements until we have finished adding this one
+ Lock lock = locks.computeIfAbsent(TimerType.ANNOUNCEMENT, k -> new ReentrantLock());
+ lock.lock();
+ try {
+ Announcement announcement = Objects.requireNonNull(announcements.computeIfAbsent(
+ Objects.hash(speak, bodyText, title), k -> new Announcement(speak, bodyText, title)));
+ announcement.devices.add(device);
+ announcement.ttsVolumes.add(ttsVolume);
+ announcement.standardVolumes.add(standardVolume);
+
+ // schedule an announcement only if it has not been scheduled before
+ timers.computeIfAbsent(TimerType.ANNOUNCEMENT,
+ k -> scheduler.schedule(this::sendAnnouncement, 500, TimeUnit.MILLISECONDS));
+ } finally {
+ lock.unlock();
}
- Announcement announcement = announcements.computeIfAbsent(Objects.hash(speak, bodyText, title),
- k -> new Announcement(speak, bodyText, title));
- announcement.devices.add(device);
- announcement.ttsVolumes.add(ttsVolume);
- announcement.standardVolumes.add(standardVolume);
- announcementTimer = scheduler.schedule(this::sendAnnouncement, 500, TimeUnit.MILLISECONDS);
}
- private synchronized void sendAnnouncement() {
- // NECESSARY TO CANCEL AND NULL TIMER?
- if (announcementTimer != null) {
- announcementTimer.cancel(true);
- announcementTimer = null;
- }
- Iterator<Announcement> iterator = announcements.values().iterator();
- while (iterator.hasNext()) {
- Announcement announcement = iterator.next();
- if (announcement != null) {
+ private void sendAnnouncement() {
+ // we lock new announcements until we have dispatched everything
+ Lock lock = locks.computeIfAbsent(TimerType.ANNOUNCEMENT, k -> new ReentrantLock());
+ lock.lock();
+ try {
+ Iterator<Announcement> iterator = announcements.values().iterator();
+ while (iterator.hasNext()) {
+ Announcement announcement = iterator.next();
try {
List<Device> devices = announcement.devices;
- if (devices != null && !devices.isEmpty()) {
+ if (!devices.isEmpty()) {
String speak = announcement.speak;
String bodyText = announcement.bodyText;
String title = announcement.title;
- List<@Nullable Integer> ttsVolumes = announcement.ttsVolumes;
- List<@Nullable Integer> standardVolumes = announcement.standardVolumes;
Map<String, Object> parameters = new HashMap<>();
parameters.put("expireAfter", "PT5S");
target.devices = targetDevices;
parameters.put("target", target);
- String accountCustomerId = this.accountCustomerId;
- String customerId = accountCustomerId == null || accountCustomerId.isEmpty()
- ? devices.toArray(new Device[0])[0].deviceOwnerCustomerId
- : accountCustomerId;
-
+ String customerId = getCustomerId(devices.get(0).deviceOwnerCustomerId);
if (customerId != null) {
parameters.put("customerId", customerId);
}
- executeSequenceCommandWithVolume(devices.toArray(new Device[0]), "AlexaAnnouncement",
- parameters, ttsVolumes.toArray(new Integer[0]),
- standardVolumes.toArray(new Integer[0]));
+ executeSequenceCommandWithVolume(devices, "AlexaAnnouncement", parameters,
+ announcement.ttsVolumes, announcement.standardVolumes);
}
} catch (Exception e) {
logger.warn("send announcement fails with unexpected error", e);
}
+ iterator.remove();
}
- iterator.remove();
+ } finally {
+ // the timer is done anyway immediately after we unlock
+ timers.remove(TimerType.ANNOUNCEMENT);
+ lock.unlock();
}
}
- public synchronized void textToSpeech(Device device, String text, @Nullable Integer ttsVolume,
+ public void textToSpeech(Device device, String text, @Nullable Integer ttsVolume,
@Nullable Integer standardVolume) {
- if (text == null || text.replaceAll("<.+?>", "").replaceAll("\\s+", " ").trim().isEmpty()) {
+ if (text.replaceAll("<.+?>", "").replaceAll("\\s+", " ").trim().isEmpty()) {
return;
}
- if (textToSpeechTimer != null) {
- textToSpeechTimer.cancel(true);
- textToSpeechTimer = null;
- }
- TextToSpeech textToSpeech = textToSpeeches.computeIfAbsent(Objects.hash(text), k -> new TextToSpeech(text));
- textToSpeech.devices.add(device);
- textToSpeech.ttsVolumes.add(ttsVolume);
- textToSpeech.standardVolumes.add(standardVolume);
- textToSpeechTimer = scheduler.schedule(this::sendTextToSpeech, 500, TimeUnit.MILLISECONDS);
- }
- private synchronized void sendTextToSpeech() {
- // NECESSARY TO CANCEL AND NULL TIMER?
- if (textToSpeechTimer != null) {
- textToSpeechTimer.cancel(true);
- textToSpeechTimer = null;
- }
- Iterator<TextToSpeech> iterator = textToSpeeches.values().iterator();
- while (iterator.hasNext()) {
- TextToSpeech textToSpeech = iterator.next();
- if (textToSpeech != null) {
+ // we lock TTS until we have finished adding this one
+ Lock lock = locks.computeIfAbsent(TimerType.TTS, k -> new ReentrantLock());
+ lock.lock();
+ try {
+ TextToSpeech textToSpeech = Objects
+ .requireNonNull(textToSpeeches.computeIfAbsent(Objects.hash(text), k -> new TextToSpeech(text)));
+ textToSpeech.devices.add(device);
+ textToSpeech.ttsVolumes.add(ttsVolume);
+ textToSpeech.standardVolumes.add(standardVolume);
+ // schedule a TTS only if it has not been scheduled before
+ timers.computeIfAbsent(TimerType.TTS,
+ k -> scheduler.schedule(this::sendTextToSpeech, 500, TimeUnit.MILLISECONDS));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void sendTextToSpeech() {
+ // we lock new TTS until we have dispatched everything
+ Lock lock = locks.computeIfAbsent(TimerType.TTS, k -> new ReentrantLock());
+ lock.lock();
+ try {
+ Iterator<TextToSpeech> iterator = textToSpeeches.values().iterator();
+ while (iterator.hasNext()) {
+ TextToSpeech textToSpeech = iterator.next();
try {
List<Device> devices = textToSpeech.devices;
- if (devices != null && !devices.isEmpty()) {
+ if (!devices.isEmpty()) {
String text = textToSpeech.text;
- List<@Nullable Integer> ttsVolumes = textToSpeech.ttsVolumes;
- List<@Nullable Integer> standardVolumes = textToSpeech.standardVolumes;
-
Map<String, Object> parameters = new HashMap<>();
parameters.put("textToSpeak", text);
- executeSequenceCommandWithVolume(devices.toArray(new Device[0]), "Alexa.Speak", parameters,
- ttsVolumes.toArray(new Integer[0]), standardVolumes.toArray(new Integer[0]));
+ executeSequenceCommandWithVolume(devices, "Alexa.Speak", parameters, textToSpeech.ttsVolumes,
+ textToSpeech.standardVolumes);
}
} catch (Exception e) {
logger.warn("send textToSpeech fails with unexpected error", e);
}
+ iterator.remove();
}
- iterator.remove();
+ } finally {
+ // the timer is done anyway immediately after we unlock
+ timers.remove(TimerType.TTS);
+ lock.unlock();
}
}
- public synchronized void volume(Device device, int vol) {
- if (volumeTimer != null) {
- volumeTimer.cancel(true);
- volumeTimer = null;
+ 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();
+ try {
+ Volume volume = Objects.requireNonNull(volumes.computeIfAbsent(vol, k -> new Volume(vol)));
+ volume.devices.add(device);
+ volume.volumes.add(vol);
+ // schedule a TTS only if it has not been scheduled before
+ timers.computeIfAbsent(TimerType.VOLUME,
+ k -> scheduler.schedule(this::sendVolume, 500, TimeUnit.MILLISECONDS));
+ } finally {
+ lock.unlock();
}
- Volume volume = volumes.computeIfAbsent(vol, k -> new Volume(vol));
- volume.devices.add(device);
- volume.volumes.add(vol);
- volumeTimer = scheduler.schedule(this::sendVolume, 500, TimeUnit.MILLISECONDS);
}
- private synchronized void sendVolume() {
- // NECESSARY TO CANCEL AND NULL TIMER?
- if (volumeTimer != null) {
- volumeTimer.cancel(true);
- volumeTimer = null;
- }
- Iterator<Volume> iterator = volumes.values().iterator();
- while (iterator.hasNext()) {
- Volume volume = iterator.next();
- if (volume != null) {
+ private void sendVolume() {
+ // we lock new volume until we have dispatched everything
+ Lock lock = locks.computeIfAbsent(TimerType.VOLUME, k -> new ReentrantLock());
+ lock.lock();
+ try {
+ Iterator<Volume> iterator = volumes.values().iterator();
+ while (iterator.hasNext()) {
+ Volume volume = iterator.next();
try {
List<Device> devices = volume.devices;
- if (devices != null && !devices.isEmpty()) {
- List<@Nullable Integer> volumes = volume.volumes;
-
- executeSequenceCommandWithVolume(devices.toArray(new Device[0]), null, null,
- volumes.toArray(new Integer[0]), null);
+ if (!devices.isEmpty()) {
+ executeSequenceCommandWithVolume(devices, null, Map.of(), volume.volumes, List.of());
}
} catch (Exception e) {
logger.warn("send volume fails with unexpected error", e);
}
+ iterator.remove();
}
- iterator.remove();
+ } finally {
+ // the timer is done anyway immediately after we unlock
+ timers.remove(TimerType.VOLUME);
+ lock.unlock();
}
}
- private void executeSequenceCommandWithVolume(@Nullable Device[] devices, @Nullable String command,
- @Nullable Map<String, Object> parameters, @NonNull Integer[] ttsVolumes,
- @Nullable Integer @Nullable [] standardVolumes) throws IOException, URISyntaxException {
+ private void executeSequenceCommandWithVolume(List<Device> devices, @Nullable String command,
+ Map<String, Object> parameters, List<@Nullable Integer> ttsVolumes,
+ List<@Nullable Integer> standardVolumes) {
JsonArray serialNodesToExecute = new JsonArray();
- if (ttsVolumes != null) {
- JsonArray ttsVolumeNodesToExecute = new JsonArray();
- for (int i = 0; i < devices.length; i++) {
- if (ttsVolumes[i] != null && (standardVolumes == null || !ttsVolumes[i].equals(standardVolumes[i]))) {
- Map<String, Object> volumeParameters = new HashMap<>();
- volumeParameters.put("value", ttsVolumes[i]);
- ttsVolumeNodesToExecute
- .add(createExecutionNode(devices[i], "Alexa.DeviceControls.Volume", volumeParameters));
- }
- }
- if (ttsVolumeNodesToExecute.size() > 0) {
- // executeSequenceNodes(devices, ttsVolumeNodesToExecute, true);
- JsonObject parallelNodesToExecute = new JsonObject();
- parallelNodesToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode");
- parallelNodesToExecute.add("nodesToExecute", ttsVolumeNodesToExecute);
- serialNodesToExecute.add(parallelNodesToExecute);
+ JsonArray ttsVolumeNodesToExecute = new JsonArray();
+ for (int i = 0; i < devices.size(); i++) {
+ Integer ttsVolume = ttsVolumes.size() > i ? ttsVolumes.get(i) : null;
+ Integer standardVolume = standardVolumes.size() > i ? standardVolumes.get(i) : null;
+ if (ttsVolume != null && (standardVolume != null || !ttsVolume.equals(standardVolume))) {
+ ttsVolumeNodesToExecute.add(
+ createExecutionNode(devices.get(i), "Alexa.DeviceControls.Volume", Map.of("value", ttsVolume)));
}
}
+ if (ttsVolumeNodesToExecute.size() > 0) {
+ JsonObject parallelNodesToExecute = new JsonObject();
+ parallelNodesToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode");
+ parallelNodesToExecute.add("nodesToExecute", ttsVolumeNodesToExecute);
+ serialNodesToExecute.add(parallelNodesToExecute);
+ }
- if (command != null && parameters != null) {
+ if (command != null && !parameters.isEmpty()) {
JsonArray commandNodesToExecute = new JsonArray();
if ("Alexa.Speak".equals(command)) {
for (Device device : devices) {
commandNodesToExecute.add(createExecutionNode(device, command, parameters));
}
} else {
- commandNodesToExecute.add(createExecutionNode(devices[0], command, parameters));
+ commandNodesToExecute.add(createExecutionNode(devices.get(0), command, parameters));
}
if (commandNodesToExecute.size() > 0) {
- // executeSequenceNodes(devices, nodesToExecute, true);
JsonObject parallelNodesToExecute = new JsonObject();
parallelNodesToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode");
parallelNodesToExecute.add("nodesToExecute", commandNodesToExecute);
}
}
- if (serialNodesToExecute.size() > 0) {
- executeSequenceNodes(devices, serialNodesToExecute, false);
+ JsonArray standardVolumeNodesToExecute = new JsonArray();
+ for (int i = 0; i < devices.size(); i++) {
+ Integer ttsVolume = ttsVolumes.size() > i ? ttsVolumes.get(i) : null;
+ Integer standardVolume = standardVolumes.size() > i ? standardVolumes.get(i) : null;
+ if (ttsVolume != null && standardVolume != null && !ttsVolume.equals(standardVolume)) {
+ standardVolumeNodesToExecute.add(createExecutionNode(devices.get(i), "Alexa.DeviceControls.Volume",
+ Map.of("value", standardVolume)));
+ }
+ }
+ if (standardVolumeNodesToExecute.size() > 0) {
+ JsonObject parallelNodesToExecute = new JsonObject();
+ parallelNodesToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode");
+ parallelNodesToExecute.add("nodesToExecute", standardVolumeNodesToExecute);
+ serialNodesToExecute.add(parallelNodesToExecute);
}
- if (standardVolumes != null) {
- JsonArray standardVolumeNodesToExecute = new JsonArray();
- for (int i = 0; i < devices.length; i++) {
- if (ttsVolumes[i] != null && standardVolumes[i] != null && !ttsVolumes[i].equals(standardVolumes[i])) {
- Map<String, @Nullable Object> volumeParameters = new HashMap<>();
- volumeParameters.put("value", standardVolumes[i]);
- standardVolumeNodesToExecute
- .add(createExecutionNode(devices[i], "Alexa.DeviceControls.Volume", volumeParameters));
- }
- }
- if (standardVolumeNodesToExecute.size() > 0) {
- executeSequenceNodes(devices, standardVolumeNodesToExecute, true);
- }
+ if (serialNodesToExecute.size() > 0) {
+ executeSequenceNodes(devices, serialNodesToExecute, false);
}
}
// commands: Alexa.Weather.Play, Alexa.Traffic.Play, Alexa.FlashBriefing.Play,
// Alexa.GoodMorning.Play,
// Alexa.SingASong.Play, Alexa.TellStory.Play, Alexa.Speak (textToSpeach)
- public void executeSequenceCommand(@Nullable Device device, String command,
- @Nullable Map<String, Object> parameters) throws IOException, URISyntaxException {
+ public void executeSequenceCommand(Device device, String command, Map<String, Object> parameters) {
JsonObject nodeToExecute = createExecutionNode(device, command, parameters);
- executeSequenceNode(new Device[] { device }, nodeToExecute);
+ executeSequenceNode(List.of(device), nodeToExecute);
+ }
+
+ private void executeSequenceNode(List<Device> devices, JsonObject nodeToExecute) {
+ QueueObject queueObject = new QueueObject();
+ queueObject.devices = devices;
+ queueObject.nodeToExecute = nodeToExecute;
+ String serialNumbers = "";
+ for (Device device : devices) {
+ String serialNumber = device.serialNumber;
+ if (serialNumber != null) {
+ Objects.requireNonNull(this.devices.computeIfAbsent(serialNumber, k -> new LinkedBlockingQueue<>()))
+ .offer(queueObject);
+ serialNumbers = serialNumbers + device.serialNumber + " ";
+ }
+ }
+ logger.debug("added {} device {}", queueObject.hashCode(), serialNumbers);
}
- private void executeSequenceNode(Device[] devices, JsonObject nodeToExecute) {
- if (devices.length == 1 && groups.values().stream().anyMatch(queueObject -> queueObject.queueRunning.get())
- || devices.length > 1
- && singles.values().stream().anyMatch(queueObject -> queueObject.queueRunning.get())) {
- if (singleGroupTimer != null) {
- singleGroupTimer.cancel(true);
- singleGroupTimer = null;
+ private void handleExecuteSequenceNode() {
+ Lock lock = locks.computeIfAbsent(TimerType.DEVICES, k -> new ReentrantLock());
+ if (lock.tryLock()) {
+ try {
+ for (String serialNumber : devices.keySet()) {
+ LinkedBlockingQueue<QueueObject> queueObjects = devices.get(serialNumber);
+ if (queueObjects != null) {
+ QueueObject queueObject = queueObjects.peek();
+ if (queueObject != null) {
+ Future<?> future = queueObject.future;
+ if (future == null || future.isDone()) {
+ boolean execute = true;
+ String serial = "";
+ for (Device tmpDevice : queueObject.devices) {
+ if (!serialNumber.equals(tmpDevice.serialNumber)) {
+ LinkedBlockingQueue<QueueObject> tmpQueueObjects = devices
+ .get(tmpDevice.serialNumber);
+ if (tmpQueueObjects != null) {
+ QueueObject tmpQueueObject = tmpQueueObjects.peek();
+ Future<?> tmpFuture = tmpQueueObject.future;
+ if (!queueObject.equals(tmpQueueObject)
+ || (tmpFuture != null && !tmpFuture.isDone())) {
+ execute = false;
+ break;
+ }
+ serial = serial + tmpDevice.serialNumber + " ";
+ }
+ }
+ }
+ if (execute) {
+ queueObject.future = scheduler.submit(() -> queuedExecuteSequenceNode(queueObject));
+ logger.debug("thread {} device {}", queueObject.hashCode(), serial);
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ lock.unlock();
}
- singleGroupTimer = scheduler.schedule(() -> executeSequenceNode(devices, nodeToExecute), 500,
- TimeUnit.MILLISECONDS);
+ }
+ }
+ private void queuedExecuteSequenceNode(QueueObject queueObject) {
+ JsonObject nodeToExecute = queueObject.nodeToExecute;
+ ExecutionNodeObject executionNodeObject = getExecutionNodeObject(nodeToExecute);
+ if (executionNodeObject == null) {
+ logger.debug("executionNodeObject empty, removing without execution");
+ removeObjectFromQueueAfterExecutionCompletion(queueObject);
return;
}
-
- if (devices.length == 1) {
- if (!singles.containsKey(devices[0])) {
- singles.put(devices[0], new QueueObject());
- }
- singles.get(devices[0]).queue.add(nodeToExecute);
+ List<String> types = executionNodeObject.types;
+ long delay = 0;
+ if (types.contains("Alexa.DeviceControls.Volume")) {
+ delay += 2000;
+ }
+ if (types.contains("Announcement")) {
+ delay += 3000;
} else {
- if (!groups.containsKey(devices[0])) {
- groups.put(devices[0], new QueueObject());
- }
- groups.get(devices[0]).queue.add(nodeToExecute);
+ delay += 2000;
}
+ try {
+ JsonObject sequenceJson = new JsonObject();
+ sequenceJson.addProperty("@type", "com.amazon.alexa.behaviors.model.Sequence");
+ sequenceJson.add("startNode", nodeToExecute);
- if (devices.length == 1 && singles.get(devices[0]).queueRunning.compareAndSet(false, true)) {
- queuedExecuteSequenceNode(devices[0], true);
- } else if (devices.length > 1 && groups.get(devices[0]).queueRunning.compareAndSet(false, true)) {
- queuedExecuteSequenceNode(devices[0], false);
- }
- }
+ JsonStartRoutineRequest request = new JsonStartRoutineRequest();
+ request.sequenceJson = gson.toJson(sequenceJson);
+ String json = gson.toJson(request);
- private void queuedExecuteSequenceNode(Device device, boolean single) {
- QueueObject queueObject = single ? singles.get(device) : groups.get(device);
- JsonObject nodeToExecute = queueObject.queue.poll();
- if (nodeToExecute != null) {
- ExecutionNodeObject executionNodeObject = getExecutionNodeObject(nodeToExecute);
- List<String> types = executionNodeObject.types;
- long delay = 0;
- if (types.contains("Alexa.DeviceControls.Volume")) {
- delay += 2000;
- }
- if (types.contains("Announcement")) {
- delay += 3000;
- } else {
- delay += 2000;
+ Map<String, String> headers = new HashMap<>();
+ headers.put("Routines-Version", "1.1.218665");
+
+ String text = executionNodeObject.text;
+ if (text != null) {
+ text = text.replaceAll("<.+?>", " ").replaceAll("\\s+", " ").trim();
+ delay += text.length() * 150;
}
- try {
- JsonObject sequenceJson = new JsonObject();
- sequenceJson.addProperty("@type", "com.amazon.alexa.behaviors.model.Sequence");
- sequenceJson.add("startNode", nodeToExecute);
- JsonStartRoutineRequest request = new JsonStartRoutineRequest();
- request.sequenceJson = gson.toJson(sequenceJson);
- String json = gson.toJson(request);
+ makeRequest("POST", alexaServer + "/api/behaviors/preview", json, true, true, null, 3);
- Map<String, String> headers = new HashMap<>();
- headers.put("Routines-Version", "1.1.218665");
+ Thread.sleep(delay);
+ } catch (IOException | URISyntaxException | InterruptedException e) {
+ logger.warn("execute sequence node fails with unexpected error", e);
+ } finally {
+ removeObjectFromQueueAfterExecutionCompletion(queueObject);
+ }
+ }
- String text = executionNodeObject.text;
- if (text != null && !text.isEmpty()) {
- text = text.replaceAll("<.+?>", " ").replaceAll("\\s+", " ").trim();
- delay += text.length() * 150;
- }
-
- makeRequest("POST", alexaServer + "/api/behaviors/preview", json, true, true, null, 3);
- } catch (IOException | URISyntaxException e) {
- logger.warn("execute sequence node fails with unexpected error", e);
- } finally {
- queueObject.senderUnblockFuture = scheduler.schedule(() -> queuedExecuteSequenceNode(device, single),
- delay, TimeUnit.MILLISECONDS);
- }
- } else {
- queueObject.dispose();
- // NECESSARY TO CANCEL AND NULL TIMER?
- if (!isSequenceNodeQueueRunning()) {
- if (singleGroupTimer != null) {
- singleGroupTimer.cancel(true);
- singleGroupTimer = null;
+ private void removeObjectFromQueueAfterExecutionCompletion(QueueObject queueObject) {
+ String serial = "";
+ for (Device device : queueObject.devices) {
+ String serialNumber = device.serialNumber;
+ if (serialNumber != null) {
+ LinkedBlockingQueue<?> queue = devices.get(serialNumber);
+ if (queue != null) {
+ queue.remove(queueObject);
}
+ serial = serial + serialNumber + " ";
}
}
+ logger.debug("removed {} device {}", queueObject.hashCode(), serial);
}
- private void executeSequenceNodes(Device[] devices, JsonArray nodesToExecute, boolean parallel)
- throws IOException, URISyntaxException {
+ private void executeSequenceNodes(List<Device> devices, JsonArray nodesToExecute, boolean parallel) {
JsonObject serialNode = new JsonObject();
if (parallel) {
serialNode.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode");
executeSequenceNode(devices, serialNode);
}
- private JsonObject createExecutionNode(@Nullable Device device, String command,
- @Nullable Map<String, Object> parameters) {
+ private JsonObject createExecutionNode(@Nullable Device device, String command, Map<String, Object> parameters) {
JsonObject operationPayload = new JsonObject();
if (device != null) {
operationPayload.addProperty("deviceType", device.deviceType);
operationPayload.addProperty("deviceSerialNumber", device.serialNumber);
operationPayload.addProperty("locale", "");
- operationPayload.addProperty("customerId",
- this.accountCustomerId == null || this.accountCustomerId.isEmpty() ? device.deviceOwnerCustomerId
- : this.accountCustomerId);
- }
- if (parameters != null) {
- for (String key : parameters.keySet()) {
- Object value = parameters.get(key);
- if (value instanceof String) {
- operationPayload.addProperty(key, (String) value);
- } else if (value instanceof Number) {
- operationPayload.addProperty(key, (Number) value);
- } else if (value instanceof Boolean) {
- operationPayload.addProperty(key, (Boolean) value);
- } else if (value instanceof Character) {
- operationPayload.addProperty(key, (Character) value);
- } else {
- operationPayload.add(key, gson.toJsonTree(value));
- }
+ operationPayload.addProperty("customerId", getCustomerId(device.deviceOwnerCustomerId));
+ }
+ for (String key : parameters.keySet()) {
+ Object value = parameters.get(key);
+ if (value instanceof String) {
+ operationPayload.addProperty(key, (String) value);
+ } else if (value instanceof Number) {
+ operationPayload.addProperty(key, (Number) value);
+ } else if (value instanceof Boolean) {
+ operationPayload.addProperty(key, (Boolean) value);
+ } else if (value instanceof Character) {
+ operationPayload.addProperty(key, (Character) value);
+ } else {
+ operationPayload.add(key, gson.toJsonTree(value));
}
}
if (parallelNodesToExecute != null && parallelNodesToExecute.size() > 0) {
JsonObject parallelNodesToExecuteJsonObject = parallelNodesToExecute.get(0)
.getAsJsonObject();
- if (parallelNodesToExecuteJsonObject != null) {
- if (parallelNodesToExecuteJsonObject.has("type")) {
- executionNodeObject.types
- .add(parallelNodesToExecuteJsonObject.get("type").getAsString());
- if (parallelNodesToExecuteJsonObject.has("operationPayload")) {
- JsonObject operationPayload = parallelNodesToExecuteJsonObject
- .getAsJsonObject("operationPayload");
- if (operationPayload != null) {
- if (operationPayload.has("textToSpeak")) {
- executionNodeObject.text = operationPayload.get("textToSpeak")
- .getAsString();
- break;
- } else if (operationPayload.has("content")) {
- JsonArray content = operationPayload.getAsJsonArray("content");
- if (content != null && content.size() > 0) {
- JsonObject contentJsonObject = content.get(0).getAsJsonObject();
- if (contentJsonObject != null && contentJsonObject.has("speak")) {
- JsonObject speak = contentJsonObject.getAsJsonObject("speak");
- if (speak != null && speak.has("value")) {
- executionNodeObject.text = speak.get("value").getAsString();
- break;
- }
- }
- }
- }
- }
- }
- }
+ if (processNodesToExecuteJsonObject(executionNodeObject,
+ parallelNodesToExecuteJsonObject)) {
+ break;
}
}
} else {
- if (serialNodesToExecuteJsonObject.has("type")) {
- executionNodeObject.types.add(serialNodesToExecuteJsonObject.get("type").getAsString());
- if (serialNodesToExecuteJsonObject.has("operationPayload")) {
- JsonObject operationPayload = serialNodesToExecuteJsonObject
- .getAsJsonObject("operationPayload");
- if (operationPayload != null) {
- if (operationPayload.has("textToSpeak")) {
- executionNodeObject.text = operationPayload.get("textToSpeak").getAsString();
- break;
- } else if (operationPayload.has("content")) {
- JsonArray content = operationPayload.getAsJsonArray("content");
- if (content != null && content.size() > 0) {
- JsonObject contentJsonObject = content.get(0).getAsJsonObject();
- if (contentJsonObject != null && contentJsonObject.has("speak")) {
- JsonObject speak = contentJsonObject.getAsJsonObject("speak");
- if (speak != null && speak.has("value")) {
- executionNodeObject.text = speak.get("value").getAsString();
- break;
- }
- }
- }
- }
- }
- }
+ if (processNodesToExecuteJsonObject(executionNodeObject, serialNodesToExecuteJsonObject)) {
+ break;
}
}
}
return executionNodeObject;
}
- public void startRoutine(Device device, String utterance) throws IOException, URISyntaxException {
+ private boolean processNodesToExecuteJsonObject(ExecutionNodeObject executionNodeObject,
+ JsonObject nodesToExecuteJsonObject) {
+ if (nodesToExecuteJsonObject.has("type")) {
+ executionNodeObject.types.add(nodesToExecuteJsonObject.get("type").getAsString());
+ if (nodesToExecuteJsonObject.has("operationPayload")) {
+ JsonObject operationPayload = nodesToExecuteJsonObject.getAsJsonObject("operationPayload");
+ if (operationPayload != null) {
+ if (operationPayload.has("textToSpeak")) {
+ executionNodeObject.text = operationPayload.get("textToSpeak").getAsString();
+ return true;
+ } else if (operationPayload.has("content")) {
+ JsonArray content = operationPayload.getAsJsonArray("content");
+ if (content != null && content.size() > 0) {
+ JsonObject contentJsonObject = content.get(0).getAsJsonObject();
+ if (contentJsonObject.has("speak")) {
+ JsonObject speak = contentJsonObject.getAsJsonObject("speak");
+ if (speak != null && speak.has("value")) {
+ executionNodeObject.text = speak.get("value").getAsString();
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public void startRoutine(Device device, String utterance)
+ throws IOException, URISyntaxException, InterruptedException {
JsonAutomation found = null;
String deviceLocale = "";
JsonAutomation[] routines = getRoutines();
if (routines == null) {
return;
}
- for (JsonAutomation routine : getRoutines()) {
+ for (JsonAutomation routine : routines) {
if (routine != null) {
Trigger[] triggers = routine.triggers;
if (triggers != null && routine.sequence != null) {
if (payload == null) {
continue;
}
- if (payload.utterance != null && payload.utterance.equalsIgnoreCase(utterance)) {
+ String payloadUtterance = payload.utterance;
+ if (payloadUtterance != null && payloadUtterance.equalsIgnoreCase(utterance)) {
found = routine;
deviceLocale = payload.locale;
break;
// "customerId": "ALEXA_CUSTOMER_ID"
String customerId = "\"customerId\":\"ALEXA_CUSTOMER_ID\"";
- String newCustomerId = "\"customerId\":\""
- + (this.accountCustomerId == null || this.accountCustomerId.isEmpty() ? device.deviceOwnerCustomerId
- : this.accountCustomerId)
- + "\"";
+ String newCustomerId = "\"customerId\":\"" + getCustomerId(device.deviceOwnerCustomerId) + "\"";
sequenceJson = sequenceJson.replace(customerId.subSequence(0, customerId.length()),
newCustomerId.subSequence(0, newCustomerId.length()));
}
}
- public @Nullable JsonAutomation @Nullable [] getRoutines() throws IOException, URISyntaxException {
+ public @Nullable JsonAutomation @Nullable [] getRoutines()
+ throws IOException, URISyntaxException, InterruptedException {
String json = makeRequestAndReturnString(alexaServer + "/api/behaviors/automations?limit=2000");
JsonAutomation[] result = parseJson(json, JsonAutomation[].class);
return result;
}
- public JsonFeed[] getEnabledFlashBriefings() throws IOException, URISyntaxException {
+ public JsonFeed[] getEnabledFlashBriefings() throws IOException, URISyntaxException, InterruptedException {
String json = makeRequestAndReturnString(alexaServer + "/api/content-skills/enabled-feeds");
JsonEnabledFeeds result = parseJson(json, JsonEnabledFeeds.class);
if (result == null) {
return new JsonFeed[0];
}
- public void setEnabledFlashBriefings(JsonFeed[] enabledFlashBriefing) throws IOException, URISyntaxException {
+ public void setEnabledFlashBriefings(JsonFeed[] enabledFlashBriefing)
+ throws IOException, URISyntaxException, InterruptedException {
JsonEnabledFeeds enabled = new JsonEnabledFeeds();
enabled.enabledFeeds = enabledFlashBriefing;
String json = gsonWithNullSerialization.toJson(enabled);
makeRequest("POST", alexaServer + "/api/content-skills/enabled-feeds", json, true, true, null, 0);
}
- public JsonNotificationSound[] getNotificationSounds(Device device) throws IOException, URISyntaxException {
+ public JsonNotificationSound[] getNotificationSounds(Device device)
+ throws IOException, URISyntaxException, InterruptedException {
String json = makeRequestAndReturnString(
alexaServer + "/api/notification/sounds?deviceSerialNumber=" + device.serialNumber + "&deviceType="
+ device.deviceType + "&softwareVersion=" + device.softwareVersion);
return new JsonNotificationSound[0];
}
- public JsonNotificationResponse[] notifications() throws IOException, URISyntaxException {
+ public JsonNotificationResponse[] notifications() throws IOException, URISyntaxException, InterruptedException {
String response = makeRequestAndReturnString(alexaServer + "/api/notifications");
JsonNotificationsResponse result = parseJson(response, JsonNotificationsResponse.class);
if (result == null) {
}
public @Nullable JsonNotificationResponse notification(Device device, String type, @Nullable String label,
- @Nullable JsonNotificationSound sound) throws IOException, URISyntaxException {
+ @Nullable JsonNotificationSound sound) throws IOException, URISyntaxException, InterruptedException {
Date date = new Date(new Date().getTime());
long createdDate = date.getTime();
Date alarm = new Date(createdDate + 5000); // add 5 seconds, because amazon does not except calls for times in
return result;
}
- public void stopNotification(JsonNotificationResponse notification) throws IOException, URISyntaxException {
+ public void stopNotification(JsonNotificationResponse notification)
+ throws IOException, URISyntaxException, InterruptedException {
makeRequestAndReturnString("DELETE", alexaServer + "/api/notifications/" + notification.id, null, true, null);
}
public @Nullable JsonNotificationResponse getNotificationState(JsonNotificationResponse notification)
- throws IOException, URISyntaxException {
+ throws IOException, URISyntaxException, InterruptedException {
String response = makeRequestAndReturnString("GET", alexaServer + "/api/notifications/" + notification.id, null,
true, null);
JsonNotificationResponse result = parseJson(response, JsonNotificationResponse.class);
}
public List<JsonMusicProvider> getMusicProviders() {
- String response;
try {
Map<String, String> headers = new HashMap<>();
headers.put("Routines-Version", "1.1.218665");
- response = makeRequestAndReturnString("GET",
+ String response = makeRequestAndReturnString("GET",
alexaServer + "/api/behaviors/entities?skillId=amzn1.ask.1p.music", null, true, headers);
- } catch (IOException | URISyntaxException e) {
+ if (!response.isEmpty()) {
+ JsonMusicProvider[] result = parseJson(response, JsonMusicProvider[].class);
+ return Arrays.asList(result);
+ }
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.warn("getMusicProviders fails: {}", e.getMessage());
- return new ArrayList<>();
}
- if (response == null || response.isEmpty()) {
- return new ArrayList<>();
- }
- JsonMusicProvider[] result = parseJson(response, JsonMusicProvider[].class);
- return Arrays.asList(result);
+ return List.of();
}
public void playMusicVoiceCommand(Device device, String providerId, String voiceCommand)
- throws IOException, URISyntaxException {
+ throws IOException, URISyntaxException, InterruptedException {
JsonPlaySearchPhraseOperationPayload payload = new JsonPlaySearchPhraseOperationPayload();
- payload.customerId = (this.accountCustomerId == null || this.accountCustomerId.isEmpty()
- ? device.deviceOwnerCustomerId
- : this.accountCustomerId);
+ payload.customerId = getCustomerId(device.deviceOwnerCustomerId);
payload.locale = "ALEXA_CURRENT_LOCALE";
payload.musicProviderId = providerId;
payload.searchPhrase = voiceCommand;
String validateResultJson = makeRequestAndReturnString("POST",
alexaServer + "/api/behaviors/operation/validate", postDataValidate, true, null);
- if (validateResultJson != null && !validateResultJson.isEmpty()) {
+ if (!validateResultJson.isEmpty()) {
JsonPlayValidationResult validationResult = parseJson(validateResultJson, JsonPlayValidationResult.class);
if (validationResult != null) {
JsonPlaySearchPhraseOperationPayload validatedOperationPayload = validationResult.operationPayload;
makeRequest("POST", alexaServer + "/api/behaviors/preview", postData, true, true, null, 3);
}
- public @Nullable JsonEqualizer getEqualizer(Device device) throws IOException, URISyntaxException {
+ public @Nullable JsonEqualizer getEqualizer(Device device)
+ throws IOException, URISyntaxException, InterruptedException {
String json = makeRequestAndReturnString(
alexaServer + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType);
return parseJson(json, JsonEqualizer.class);
}
- public void setEqualizer(Device device, JsonEqualizer settings) throws IOException, URISyntaxException {
+ public void setEqualizer(Device device, JsonEqualizer settings)
+ throws IOException, URISyntaxException, InterruptedException {
String postData = gson.toJson(settings);
makeRequest("POST", alexaServer + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType, postData,
true, true, null, 0);
}
- @NonNullByDefault
private static class Announcement {
public List<Device> devices = new ArrayList<>();
public String speak;
}
}
- @NonNullByDefault
private static class TextToSpeech {
public List<Device> devices = new ArrayList<>();
public String text;
}
}
- @NonNullByDefault
private static class Volume {
public List<Device> devices = new ArrayList<>();
public int volume;
}
}
- @NonNullByDefault
private static class QueueObject {
- public LinkedBlockingQueue<JsonObject> queue = new LinkedBlockingQueue<>();
- public AtomicBoolean queueRunning = new AtomicBoolean();
- public @Nullable ScheduledFuture<?> senderUnblockFuture;
-
- public void dispose() {
- queue.clear();
- queueRunning.set(false);
- if (senderUnblockFuture != null) {
- senderUnblockFuture.cancel(true);
- }
- }
+ public @Nullable Future<?> future;
+ public List<Device> devices = List.of();
+ public JsonObject nodeToExecute = new JsonObject();
}
- @NonNullByDefault
private static class ExecutionNodeObject {
public List<String> types = new ArrayList<>();
@Nullable
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amazonechocontrol.internal.Connection;
*/
@NonNullByDefault
public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
-
private final Logger logger = LoggerFactory.getLogger(EchoHandler.class);
private Gson gson;
private @Nullable Device device;
}
}
if (volume != null) {
- if (StringUtils.equals(device.deviceFamily, "WHA")) {
+ if ("WHA".equals(device.deviceFamily)) {
connection.command(device, "{\"type\":\"VolumeLevelCommand\",\"volumeLevel\":" + volume
+ ",\"contentFocusClientId\":\"Default\"}");
} else {
if (channelId.equals(CHANNEL_MUSIC_PROVIDER_ID)) {
if (command instanceof StringType) {
waitForUpdate = 0;
- String musicProviderId = ((StringType) command).toFullString();
- if (!StringUtils.equals(musicProviderId, this.musicProviderId)) {
+ String musicProviderId = command.toFullString();
+ if (!musicProviderId.equals(this.musicProviderId)) {
this.musicProviderId = musicProviderId;
if (this.isPlaying) {
connection.playMusicVoiceCommand(device, this.musicProviderId, "!");
}
if (channelId.equals(CHANNEL_PLAY_MUSIC_VOICE_COMMAND)) {
if (command instanceof StringType) {
- String voiceCommand = ((StringType) command).toFullString();
+ String voiceCommand = command.toFullString();
if (!this.musicProviderId.isEmpty()) {
connection.playMusicVoiceCommand(device, this.musicProviderId, voiceCommand);
waitForUpdate = 3000;
waitForUpdate = 4000;
String bluetoothId = lastKnownBluetoothMAC;
BluetoothState state = bluetoothState;
- if (state != null && (StringUtils.isEmpty(bluetoothId))) {
+ if (state != null && (bluetoothId == null || bluetoothId.isEmpty())) {
PairedDevice[] pairedDeviceList = state.pairedDeviceList;
if (pairedDeviceList != null) {
for (PairedDevice paired : pairedDeviceList) {
if (paired == null) {
continue;
}
- if (StringUtils.isNotEmpty(paired.address)) {
- lastKnownBluetoothMAC = paired.address;
+ String pairedAddress = paired.address;
+ if (pairedAddress != null && !pairedAddress.isEmpty()) {
+ lastKnownBluetoothMAC = pairedAddress;
break;
}
}
}
}
- if (StringUtils.isNotEmpty(lastKnownBluetoothMAC)) {
+ if (lastKnownBluetoothMAC != null && !lastKnownBluetoothMAC.isEmpty()) {
connection.bluetooth(device, lastKnownBluetoothMAC);
}
} else if (command == OnOffType.OFF) {
// amazon music commands
if (channelId.equals(CHANNEL_AMAZON_MUSIC_TRACK_ID)) {
if (command instanceof StringType) {
- String trackId = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(trackId)) {
+ String trackId = command.toFullString();
+ if (!trackId.isEmpty()) {
waitForUpdate = 3000;
}
connection.playAmazonMusicTrack(device, trackId);
}
if (channelId.equals(CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID)) {
if (command instanceof StringType) {
- String playListId = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(playListId)) {
+ String playListId = command.toFullString();
+ if (!playListId.isEmpty()) {
waitForUpdate = 3000;
}
connection.playAmazonMusicPlayList(device, playListId);
if (channelId.equals(CHANNEL_AMAZON_MUSIC)) {
if (command == OnOffType.ON) {
String lastKnownAmazonMusicId = this.lastKnownAmazonMusicId;
- if (StringUtils.isNotEmpty(lastKnownAmazonMusicId)) {
+ if (lastKnownAmazonMusicId != null && !lastKnownAmazonMusicId.isEmpty()) {
waitForUpdate = 3000;
}
connection.playAmazonMusicTrack(device, lastKnownAmazonMusicId);
// radio commands
if (channelId.equals(CHANNEL_RADIO_STATION_ID)) {
if (command instanceof StringType) {
- String stationId = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(stationId)) {
+ String stationId = command.toFullString();
+ if (!stationId.isEmpty()) {
waitForUpdate = 3000;
}
connection.playRadio(device, stationId);
if (channelId.equals(CHANNEL_RADIO)) {
if (command == OnOffType.ON) {
String lastKnownRadioStationId = this.lastKnownRadioStationId;
- if (StringUtils.isNotEmpty(lastKnownRadioStationId)) {
+ if (lastKnownRadioStationId != null && !lastKnownRadioStationId.isEmpty()) {
waitForUpdate = 3000;
}
connection.playRadio(device, lastKnownRadioStationId);
if (channelId.equals(CHANNEL_REMIND)) {
if (command instanceof StringType) {
stopCurrentNotification();
- String reminder = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(reminder)) {
+ String reminder = command.toFullString();
+ if (!reminder.isEmpty()) {
waitForUpdate = 3000;
updateRemind = true;
currentNotification = connection.notification(device, "Reminder", reminder, null);
if (channelId.equals(CHANNEL_PLAY_ALARM_SOUND)) {
if (command instanceof StringType) {
stopCurrentNotification();
- String alarmSound = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(alarmSound)) {
+ String alarmSound = command.toFullString();
+ if (!alarmSound.isEmpty()) {
waitForUpdate = 3000;
updateAlarm = true;
String[] parts = alarmSound.split(":", 2);
// routine commands
if (channelId.equals(CHANNEL_TEXT_TO_SPEECH)) {
if (command instanceof StringType) {
- String text = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(text)) {
+ String text = command.toFullString();
+ if (!text.isEmpty()) {
waitForUpdate = 1000;
updateTextToSpeech = true;
startTextToSpeech(connection, device, text);
}
if (channelId.equals(CHANNEL_LAST_VOICE_COMMAND)) {
if (command instanceof StringType) {
- String text = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(text)) {
+ String text = command.toFullString();
+ if (!text.isEmpty()) {
waitForUpdate = -1;
startTextToSpeech(connection, device, text);
}
}
if (channelId.equals(CHANNEL_START_COMMAND)) {
if (command instanceof StringType) {
- String commandText = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(commandText)) {
+ String commandText = command.toFullString();
+ if (!commandText.isEmpty()) {
updateStartCommand = true;
if (commandText.startsWith(FLASH_BRIEFING_COMMAND_PREFIX)) {
// Handle custom flashbriefings commands
- String flashbriefing = commandText.substring(FLASH_BRIEFING_COMMAND_PREFIX.length());
-
- for (FlashBriefingProfileHandler flashBriefing : account
+ String flashBriefingId = commandText.substring(FLASH_BRIEFING_COMMAND_PREFIX.length());
+ for (FlashBriefingProfileHandler flashBriefingHandler : account
.getFlashBriefingProfileHandlers()) {
- ThingUID flashBriefingId = flashBriefing.getThing().getUID();
- if (StringUtils.equals(flashBriefing.getThing().getUID().getId(), flashbriefing)) {
- flashBriefing.handleCommand(new ChannelUID(flashBriefingId, CHANNEL_PLAY_ON_DEVICE),
+ ThingUID flashBriefingUid = flashBriefingHandler.getThing().getUID();
+ if (flashBriefingId.equals(flashBriefingHandler.getThing().getUID().getId())) {
+ flashBriefingHandler.handleCommand(
+ new ChannelUID(flashBriefingUid, CHANNEL_PLAY_ON_DEVICE),
new StringType(device.serialNumber));
break;
}
commandText = "Alexa." + commandText + ".Play";
}
waitForUpdate = 1000;
- connection.executeSequenceCommand(device, commandText, null);
+ connection.executeSequenceCommand(device, commandText, Map.of());
}
}
}
}
if (channelId.equals(CHANNEL_START_ROUTINE)) {
if (command instanceof StringType) {
- String utterance = ((StringType) command).toFullString();
- if (StringUtils.isNotEmpty(utterance)) {
+ String utterance = command.toFullString();
+ if (!utterance.isEmpty()) {
waitForUpdate = 1000;
updateRoutine = true;
connection.startRoutine(device, utterance);
} else {
this.updateStateJob = scheduler.schedule(doRefresh, waitForUpdate, TimeUnit.MILLISECONDS);
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.info("handleCommand fails", e);
}
}
try {
connection.setEqualizer(device, newEqualizerSetting);
return true;
- } catch (HttpException | IOException | ConnectionException e) {
+ } catch (HttpException | IOException | ConnectionException | InterruptedException e) {
logger.debug("Update equalizer failed", e);
this.lastKnownEqualizer = null;
}
if (currentConnection != null) {
try {
currentConnection.stopNotification(currentNotification);
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.warn("Stop notification failed", e);
}
}
}
}
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.warn("update notification state fails", e);
}
if (stopCurrentNotification) {
if (musicProviderId != null) {
musicProviderId = musicProviderId.toUpperCase();
- if (StringUtils.equals(musicProviderId, "AMAZON MUSIC")) {
+ if (musicProviderId.equals("AMAZON MUSIC")) {
musicProviderId = "AMAZON_MUSIC";
}
- if (StringUtils.equals(musicProviderId, "CLOUD_PLAYER")) {
+ if (musicProviderId.equals("CLOUD_PLAYER")) {
musicProviderId = "AMAZON_MUSIC";
}
- if (StringUtils.startsWith(musicProviderId, "TUNEIN")) {
+ if (musicProviderId.startsWith("TUNEIN")) {
musicProviderId = "TUNEIN";
}
- if (StringUtils.startsWithIgnoreCase(musicProviderId, "iHeartRadio")) {
+ if (musicProviderId.startsWith("IHEARTRADIO")) {
musicProviderId = "I_HEART_RADIO";
}
- if (StringUtils.containsIgnoreCase(musicProviderId, "Apple")
- && StringUtils.containsIgnoreCase(musicProviderId, "Music")) {
+ if (musicProviderId.equals("APPLE") && musicProviderId.contains("MUSIC")) {
musicProviderId = "APPLE_MUSIC";
}
}
if (e.getCode() != 400) {
logger.info("getPlayer fails", e);
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.info("getPlayer fails", e);
}
// check playing
- isPlaying = (playerInfo != null && StringUtils.equals(playerInfo.state, "PLAYING"));
+ isPlaying = (playerInfo != null && "PLAYING".equals(playerInfo.state));
- isPaused = (playerInfo != null && StringUtils.equals(playerInfo.state, "PAUSED"));
+ isPaused = (playerInfo != null && "PAUSED".equals(playerInfo.state));
synchronized (progressLock) {
Boolean showTime = null;
Long mediaLength = null;
JsonMediaState mediaState = null;
try {
- if (StringUtils.equalsIgnoreCase(musicProviderId, "AMAZON_MUSIC")
- || StringUtils.equalsIgnoreCase(musicProviderId, "TUNEIN")) {
+ if ("AMAZON_MUSIC".equalsIgnoreCase(musicProviderId) || "TUNEIN".equalsIgnoreCase(musicProviderId)) {
mediaState = connection.getMediaState(device);
}
} catch (HttpException e) {
} else {
logger.info("getMediaState fails", e);
}
- } catch (IOException | URISyntaxException e) {
+ } catch (IOException | URISyntaxException | InterruptedException e) {
logger.info("getMediaState fails", e);
}
String amazonMusicTrackId = "";
String amazonMusicPlayListId = "";
boolean amazonMusic = false;
- if (mediaState != null && isPlaying && StringUtils.equals(mediaState.providerId, "CLOUD_PLAYER")
- && StringUtils.isNotEmpty(mediaState.contentId)) {
- amazonMusicTrackId = mediaState.contentId;
- lastKnownAmazonMusicId = amazonMusicTrackId;
- amazonMusic = true;
+ if (mediaState != null) {
+ String contentId = mediaState.contentId;
+ if (isPlaying && "CLOUD_PLAYER".equals(mediaState.providerId) && contentId != null
+ && !contentId.isEmpty()) {
+ amazonMusicTrackId = contentId;
+ lastKnownAmazonMusicId = amazonMusicTrackId;
+ amazonMusic = true;
+ }
}
// handle bluetooth
if (paired == null) {
continue;
}
- if (paired.connected && paired.address != null) {
+ String pairedAddress = paired.address;
+ if (paired.connected && pairedAddress != null) {
bluetoothIsConnected = true;
- bluetoothMAC = paired.address;
+ bluetoothMAC = pairedAddress;
bluetoothDeviceName = paired.friendlyName;
- if (StringUtils.isEmpty(bluetoothDeviceName)) {
- bluetoothDeviceName = paired.address;
+ if (bluetoothDeviceName == null || bluetoothDeviceName.isEmpty()) {
+ bluetoothDeviceName = pairedAddress;
}
break;
}
}
}
}
- if (StringUtils.isNotEmpty(bluetoothMAC)) {
+ if (!bluetoothMAC.isEmpty()) {
lastKnownBluetoothMAC = bluetoothMAC;
}
// handle radio
boolean isRadio = false;
- if (mediaState != null && StringUtils.isNotEmpty(mediaState.radioStationId)) {
- lastKnownRadioStationId = mediaState.radioStationId;
- if (StringUtils.equalsIgnoreCase(musicProviderId, "TUNEIN")) {
- isRadio = true;
- }
- }
String radioStationId = "";
- if (isRadio && mediaState != null && StringUtils.equals(mediaState.currentState, "PLAYING")
- && mediaState.radioStationId != null) {
- radioStationId = mediaState.radioStationId;
+ if (mediaState != null) {
+ radioStationId = Objects.requireNonNullElse(mediaState.radioStationId, "");
+ if (!radioStationId.isEmpty()) {
+ lastKnownRadioStationId = radioStationId;
+ if ("TUNEIN".equalsIgnoreCase(musicProviderId)) {
+ isRadio = true;
+ if (!"PLAYING".equals(mediaState.currentState)) {
+ radioStationId = "";
+ }
+ }
+ }
}
// handle title, subtitle, imageUrl
QueueEntry entry = queueEntries[0];
if (entry != null) {
if (isRadio) {
- if (StringUtils.isEmpty(imageUrl) && entry.imageURL != null) {
+ if ((imageUrl == null || imageUrl.isEmpty()) && entry.imageURL != null) {
imageUrl = entry.imageURL;
}
- if (StringUtils.isEmpty(subTitle1) && entry.radioStationSlogan != null) {
+ if ((subTitle1 == null || subTitle1.isEmpty()) && entry.radioStationSlogan != null) {
subTitle1 = entry.radioStationSlogan;
}
- if (StringUtils.isEmpty(subTitle2) && entry.radioStationLocation != null) {
+ if ((subTitle2 == null || subTitle2.isEmpty()) && entry.radioStationLocation != null) {
subTitle2 = entry.radioStationLocation;
}
}
String providerDisplayName = "";
if (provider != null) {
if (provider.providerDisplayName != null) {
- providerDisplayName = provider.providerDisplayName;
+ providerDisplayName = Objects.requireNonNullElse(provider.providerDisplayName, providerDisplayName);
}
- if (StringUtils.isNotEmpty(provider.providerName) && StringUtils.isEmpty(providerDisplayName)) {
+ String providerName = provider.providerName;
+ if (providerName != null && !providerName.isEmpty() && providerDisplayName.isEmpty()) {
providerDisplayName = provider.providerName;
}
}
treble = equalizer.treble;
}
this.lastKnownEqualizer = equalizer;
- } catch (IOException | URISyntaxException | HttpException | ConnectionException e) {
+ } catch (IOException | URISyntaxException | HttpException | ConnectionException | InterruptedException e) {
logger.debug("Get equalizer failes", e);
return;
}
return;
}
Description description = pushActivity.parseDescription();
- if (StringUtils.isEmpty(description.firstUtteranceId)
- || StringUtils.startsWithIgnoreCase(description.firstUtteranceId, "TextClient:")) {
+ String firstUtteranceId = description.firstUtteranceId;
+ if (firstUtteranceId == null || firstUtteranceId.isEmpty()
+ || firstUtteranceId.toLowerCase().startsWith("textclient:")) {
return;
}
- if (StringUtils.isEmpty(description.firstStreamId)) {
+ String firstStreamId = description.firstStreamId;
+ if (firstStreamId == null || firstStreamId.isEmpty()) {
return;
}
String spokenText = description.summary;
- if (spokenText != null && StringUtils.isNotEmpty(spokenText)) {
+ if (spokenText != null && !spokenText.isEmpty()) {
// remove wake word
String wakeWordPrefix = this.wakeWord;
if (wakeWordPrefix != null) {
wakeWordPrefix += " ";
- if (StringUtils.startsWithIgnoreCase(spokenText, wakeWordPrefix)) {
+ if (spokenText.toLowerCase().startsWith(wakeWordPrefix.toLowerCase())) {
spokenText = spokenText.substring(wakeWordPrefix.length());
}
}
this.logger.debug("Handle push command {}", command);
switch (command) {
case "PUSH_VOLUME_CHANGE":
- JsonCommandPayloadPushVolumeChange volumeChange = gson.fromJson(payload,
- JsonCommandPayloadPushVolumeChange.class);
+ JsonCommandPayloadPushVolumeChange volumeChange = Objects
+ .requireNonNull(gson.fromJson(payload, JsonCommandPayloadPushVolumeChange.class));
Connection connection = this.findConnection();
- @Nullable
Integer volumeSetting = volumeChange.volumeSetting;
- @Nullable
Boolean muted = volumeChange.isMuted;
if (muted != null && muted) {
updateState(CHANNEL_VOLUME, new PercentType(0));
ZonedDateTime nextMusicAlarm = null;
ZonedDateTime nextTimer = null;
for (JsonNotificationResponse notification : notifications) {
- if (StringUtils.equals(notification.deviceSerialNumber, device.serialNumber)) {
+ if (Objects.equals(notification.deviceSerialNumber, device.serialNumber)) {
// notification for this device
- if (StringUtils.equals(notification.status, "ON")) {
+ if ("ON".equals(notification.status)) {
if ("Reminder".equals(notification.type)) {
String offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()).toString();
ZonedDateTime alarmTime = ZonedDateTime
.parse(notification.originalDate + "T" + notification.originalTime + offset);
- if (StringUtils.isNotBlank(notification.recurringPattern) && alarmTime.isBefore(now)) {
+ String recurringPattern = notification.recurringPattern;
+ if (recurringPattern != null && !recurringPattern.isBlank() && alarmTime.isBefore(now)) {
continue; // Ignore recurring entry if alarm time is before now
}
if (nextReminder == null || alarmTime.isBefore(nextReminder)) {
String offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()).toString();
ZonedDateTime alarmTime = ZonedDateTime
.parse(notification.originalDate + "T" + notification.originalTime + offset);
- if (StringUtils.isNotBlank(notification.recurringPattern) && alarmTime.isBefore(now)) {
+ String recurringPattern = notification.recurringPattern;
+ if (recurringPattern != null && !recurringPattern.isBlank() && alarmTime.isBefore(now)) {
continue; // Ignore recurring entry if alarm time is before now
}
if (nextAlarm == null || alarmTime.isBefore(nextAlarm)) {
String offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()).toString();
ZonedDateTime alarmTime = ZonedDateTime
.parse(notification.originalDate + "T" + notification.originalTime + offset);
- if (StringUtils.isNotBlank(notification.recurringPattern) && alarmTime.isBefore(now)) {
+ String recurringPattern = notification.recurringPattern;
+ if (recurringPattern != null && !recurringPattern.isBlank() && alarmTime.isBefore(now)) {
continue; // Ignore recurring entry if alarm time is before now
}
if (nextMusicAlarm == null || alarmTime.isBefore(nextMusicAlarm)) {