From 7a5802b09a6498da441d5da45a613a8243b904d6 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Tue, 6 Feb 2024 17:18:02 +0100 Subject: [PATCH 01/13] [network] Improve threading (#16315) * [network] Improve threading * Use timeouts with CompletableFutures * Use seperate executor when waiting for results * Catch exceptions when joining CompletableFutures * Stop previous detection when starting a new one Fixes #16305 Signed-off-by: Wouter Born --- .../network/internal/PresenceDetection.java | 55 +++++++++++++++---- .../internal/PresenceDetectionTest.java | 15 +++-- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java index f6b4cd2b60e75..04541ebf26eed 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java @@ -25,7 +25,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -92,6 +94,7 @@ public class PresenceDetection implements IPRequestReceivedCallback { private Set networkInterfaceNames = Set.of(); private @Nullable ScheduledFuture refreshJob; protected @Nullable ExecutorService detectionExecutorService; + protected @Nullable ExecutorService waitForResultExecutorService; private String dhcpState = "off"; int detectionChecks; private String lastReachableNetworkInterfaceName = ""; @@ -295,6 +298,21 @@ private void withDestinationAddress(Consumer consumer) { } } + private void stopDetection() { + ExecutorService detectionExecutorService = this.detectionExecutorService; + if (detectionExecutorService != null) { + logger.debug("Shutting down detectionExecutorService"); + detectionExecutorService.shutdownNow(); + this.detectionExecutorService = null; + } + ExecutorService waitForResultExecutorService = this.waitForResultExecutorService; + if (waitForResultExecutorService != null) { + logger.debug("Shutting down waitForResultExecutorService"); + waitForResultExecutorService.shutdownNow(); + this.waitForResultExecutorService = null; + } + } + /** * Perform a presence detection with ICMP-, ARP ping and TCP connection attempts simultaneously. * A fixed thread pool will be created with as many threads as necessary to perform all tests at once. @@ -333,50 +351,61 @@ public CompletableFuture performPresenceDetection() { return CompletableFuture.completedFuture(pdv); } + stopDetection(); + ExecutorService detectionExecutorService = getThreadsFor(detectionChecks); this.detectionExecutorService = detectionExecutorService; + ExecutorService waitForResultExecutorService = getThreadsFor(1); + this.waitForResultExecutorService = waitForResultExecutorService; List> completableFutures = new ArrayList<>(); for (Integer tcpPort : tcpPorts) { - completableFutures.add(CompletableFuture.runAsync(() -> { + addAsyncDetection(completableFutures, () -> { Thread.currentThread().setName("presenceDetectionTCP_" + hostname + " " + tcpPort); performServicePing(pdv, tcpPort); - }, detectionExecutorService)); + }, detectionExecutorService); } // ARP ping for IPv4 addresses. Use single executor for Windows tool and // each own executor for each network interface for other tools if (arpPingMethod == ArpPingUtilEnum.ELI_FULKERSON_ARP_PING_FOR_WINDOWS) { - completableFutures.add(CompletableFuture.runAsync(() -> { + addAsyncDetection(completableFutures, () -> { Thread.currentThread().setName("presenceDetectionARP_" + hostname + " "); // arp-ping.exe tool capable of handling multiple interfaces by itself performArpPing(pdv, ""); - }, detectionExecutorService)); + }, detectionExecutorService); } else if (interfaceNames != null) { for (final String interfaceName : interfaceNames) { - completableFutures.add(CompletableFuture.runAsync(() -> { + addAsyncDetection(completableFutures, () -> { Thread.currentThread().setName("presenceDetectionARP_" + hostname + " " + interfaceName); performArpPing(pdv, interfaceName); - }, detectionExecutorService)); + }, detectionExecutorService); } } // ICMP ping if (pingMethod != null) { - completableFutures.add(CompletableFuture.runAsync(() -> { + addAsyncDetection(completableFutures, () -> { Thread.currentThread().setName("presenceDetectionICMP_" + hostname); if (pingMethod == IpPingMethodEnum.JAVA_PING) { performJavaPing(pdv); } else { performSystemPing(pdv); } - }, detectionExecutorService)); + }, detectionExecutorService); } return CompletableFuture.supplyAsync(() -> { + Thread.currentThread().setName("presenceDetectionResult_" + hostname); logger.debug("Waiting for {} detection futures for {} to complete", completableFutures.size(), hostname); - completableFutures.forEach(CompletableFuture::join); + completableFutures.forEach(completableFuture -> { + try { + completableFuture.join(); + } catch (CancellationException | CompletionException e) { + logger.debug("Detection future failed to complete", e); + } + }); logger.debug("All {} detection futures for {} have completed", completableFutures.size(), hostname); if (!pdv.isReachable()) { @@ -392,7 +421,13 @@ public CompletableFuture performPresenceDetection() { detectionChecks = 0; return pdv; - }, scheduledExecutorService); + }, waitForResultExecutorService); + } + + private void addAsyncDetection(List> completableFutures, Runnable detectionRunnable, + ExecutorService executorService) { + completableFutures.add(CompletableFuture.runAsync(detectionRunnable, executorService) + .orTimeout(timeout.plusSeconds(3).toMillis(), TimeUnit.MILLISECONDS)); } /** diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java index bf9d09fc6a8e7..0f770e9157a21 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java @@ -53,6 +53,7 @@ public class PresenceDetectionTest { private @Mock @NonNullByDefault({}) Consumer callback; private @Mock @NonNullByDefault({}) ExecutorService detectionExecutorService; + private @Mock @NonNullByDefault({}) ExecutorService waitForResultExecutorService; private @Mock @NonNullByDefault({}) ScheduledExecutorService scheduledExecutorService; private @Mock @NonNullByDefault({}) PresenceDetectionListener listener; private @Mock @NonNullByDefault({}) NetworkUtils networkUtils; @@ -90,6 +91,8 @@ public void threadCountTest() { doNothing().when(subject).performSystemPing(any()); doNothing().when(subject).performServicePing(any(), anyInt()); + doReturn(waitForResultExecutorService).when(subject).getThreadsFor(1); + subject.getValue(callback -> { }); @@ -99,7 +102,7 @@ public void threadCountTest() { // "Wait" for the presence detection to finish ArgumentCaptor runnableCapture = ArgumentCaptor.forClass(Runnable.class); - verify(scheduledExecutorService, times(1)).execute(runnableCapture.capture()); + verify(waitForResultExecutorService, times(1)).execute(runnableCapture.capture()); runnableCapture.getValue().run(); assertThat(subject.detectionChecks, is(0)); @@ -114,7 +117,8 @@ public void partialAndFinalCallbackTests() throws InterruptedException, IOExcept anyString(), any(), any()); doReturn(pingResult).when(networkUtils).servicePing(anyString(), anyInt(), any()); - doReturn(detectionExecutorService).when(subject).getThreadsFor(anyInt()); + doReturn(detectionExecutorService).when(subject).getThreadsFor(3); + doReturn(waitForResultExecutorService).when(subject).getThreadsFor(1); subject.performPresenceDetection(); @@ -129,7 +133,7 @@ public void partialAndFinalCallbackTests() throws InterruptedException, IOExcept // "Wait" for the presence detection to finish ArgumentCaptor runnableCapture = ArgumentCaptor.forClass(Runnable.class); - verify(scheduledExecutorService, times(1)).execute(runnableCapture.capture()); + verify(waitForResultExecutorService, times(1)).execute(runnableCapture.capture()); runnableCapture.getValue().run(); assertThat(subject.detectionChecks, is(0)); @@ -154,7 +158,8 @@ public void cacheTest() throws InterruptedException, IOException { anyString(), any(), any()); doReturn(pingResult).when(networkUtils).servicePing(anyString(), anyInt(), any()); - doReturn(detectionExecutorService).when(subject).getThreadsFor(anyInt()); + doReturn(detectionExecutorService).when(subject).getThreadsFor(3); + doReturn(waitForResultExecutorService).when(subject).getThreadsFor(1); // We expect no valid value assertTrue(subject.cache.isExpired()); @@ -174,7 +179,7 @@ public void cacheTest() throws InterruptedException, IOException { // "Wait" for the presence detection to finish capture = ArgumentCaptor.forClass(Runnable.class); - verify(scheduledExecutorService, times(1)).execute(capture.capture()); + verify(waitForResultExecutorService, times(1)).execute(capture.capture()); capture.getValue().run(); // Although there are multiple partial results and a final result, From 8b2148e121a18b01c1185416144abaa969eac784 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 6 Feb 2024 20:33:32 +0100 Subject: [PATCH 02/13] [miio] Fix date parsing issue last cleaning details (#16380) https://community.openhab.org/t/miio-binding-roborock-s4max-no-longer-getting-last-cleaning-details/153260/3 Signed-off-by: Marcel Verpaalen --- .../miio/internal/handler/MiIoVacuumHandler.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java index cae9bb8a76765..8474f8ed545c2 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java @@ -541,12 +541,14 @@ private void updateHistoryRecordLegacy(JsonArray historyData) { private void updateHistoryRecord(HistoryRecordDTO historyRecordDTO) { JsonObject historyRecord = GSON.toJsonTree(historyRecordDTO).getAsJsonObject(); if (historyRecordDTO.getStart() != null) { - historyRecord.addProperty("start", historyRecordDTO.getStart().split("\\+")[0]); - updateState(CHANNEL_HISTORY_START_TIME, new DateTimeType(historyRecordDTO.getStart().split("\\+")[0])); + historyRecord.addProperty("start", historyRecordDTO.getStart().split("\\+")[0].split("\\-")[0]); + updateState(CHANNEL_HISTORY_START_TIME, + new DateTimeType(historyRecordDTO.getStart().split("\\+")[0].split("\\-")[0])); } if (historyRecordDTO.getEnd() != null) { - historyRecord.addProperty("end", historyRecordDTO.getEnd().split("\\+")[0]); - updateState(CHANNEL_HISTORY_END_TIME, new DateTimeType(historyRecordDTO.getEnd().split("\\+")[0])); + historyRecord.addProperty("end", historyRecordDTO.getEnd().split("\\+")[0].split("\\-")[0]); + updateState(CHANNEL_HISTORY_END_TIME, + new DateTimeType(historyRecordDTO.getEnd().split("\\+")[0].split("\\-")[0])); } if (historyRecordDTO.getDuration() != null) { long duration = TimeUnit.SECONDS.toMinutes(historyRecordDTO.getDuration().longValue()); From eff44960a4916fa2377276d23154ecb9f72c43ad Mon Sep 17 00:00:00 2001 From: joerg1985 <16140691+joerg1985@users.noreply.github.com> Date: Thu, 8 Feb 2024 07:49:03 +0100 Subject: [PATCH 03/13] [rrd4j] Reuse the state for identical values (#16379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Sautter --- .../rrd4j/internal/RRD4jPersistenceService.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.persistence.rrd4j/src/main/java/org/openhab/persistence/rrd4j/internal/RRD4jPersistenceService.java b/bundles/org.openhab.persistence.rrd4j/src/main/java/org/openhab/persistence/rrd4j/internal/RRD4jPersistenceService.java index 0fedb56922ff3..c8ee865a1cdd2 100644 --- a/bundles/org.openhab.persistence.rrd4j/src/main/java/org/openhab/persistence/rrd4j/internal/RRD4jPersistenceService.java +++ b/bundles/org.openhab.persistence.rrd4j/src/main/java/org/openhab/persistence/rrd4j/internal/RRD4jPersistenceService.java @@ -491,9 +491,21 @@ public Iterable query(FilterCriteria filter) { long ts = result.getFirstTimestamp(); ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochSecond(ts), ZoneId.systemDefault()); long step = result.getRowCount() > 1 ? result.getStep() : 0; + + double prevValue = Double.NaN; + State prevState = null; for (double value : result.getValues(DATASOURCE_STATE)) { if (!Double.isNaN(value) && (((ts >= start) && (ts <= end)) || (start == end))) { - RRD4jItem rrd4jItem = new RRD4jItem(itemName, toState.apply(value), zdt); + State state; + + if (prevValue == value) { + state = prevState; + } else { + prevState = state = toState.apply(value); + prevValue = value; + } + + RRD4jItem rrd4jItem = new RRD4jItem(itemName, state, zdt); items.add(rrd4jItem); } zdt = zdt.plusSeconds(step); From b36dc180d6deda9d9437ec68b44b955c128ea9dc Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 8 Feb 2024 23:11:57 +0100 Subject: [PATCH 04/13] [miio] Fix emtpy command sending for feature channels (#16384) closes #15994 Signed-off-by: Marcel Verpaalen --- .../binding/miio/internal/handler/MiIoVacuumHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java index 8474f8ed545c2..09e15bd4c7961 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java @@ -642,7 +642,7 @@ protected synchronized void updateData() { } } for (RobotCababilities cmd : FEATURES_CHANNELS) { - if (isLinked(cmd.getChannel())) { + if (isLinked(cmd.getChannel()) && !cmd.getCommand().isBlank()) { sendCommand(cmd.getCommand()); } } From dd6d8c1bd2ef21e4533e38bff5483b1ee6d54a67 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 9 Feb 2024 09:41:52 +0100 Subject: [PATCH 05/13] [miio] Fix last cleaning details end missing (#16388) Signed-off-by: Marcel Verpaalen --- .../binding/miio/internal/handler/MiIoVacuumHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java index 09e15bd4c7961..b0cb52ef8193d 100644 --- a/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java +++ b/bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoVacuumHandler.java @@ -506,7 +506,7 @@ private void updateHistoryRecordLegacy(JsonArray historyData) { .toString()); break; case 1: - historyRecord.setStart(ZonedDateTime + historyRecord.setEnd(ZonedDateTime .ofInstant(Instant.ofEpochSecond(value.longValue()), ZoneId.systemDefault()) .toString()); break; From e4d5ae0a87baecbcd55098d30d0031585d8db36d Mon Sep 17 00:00:00 2001 From: morph166955 <53797132+morph166955@users.noreply.github.com> Date: Fri, 9 Feb 2024 03:05:48 -0600 Subject: [PATCH 06/13] [androidtv] Add PhilipsTV protocol Signed-off-by: Ben Rosenblum Signed-off-by: morph166955 <53797132+morph166955@users.noreply.github.com> --- bundles/org.openhab.binding.androidtv/NOTICE | 18 + .../org.openhab.binding.androidtv/README.md | 126 +++- bundles/org.openhab.binding.androidtv/pom.xml | 34 + .../src/main/feature/feature.xml | 1 + .../internal/AndroidTVBindingConstants.java | 22 +- ...roidTVDynamicStateDescriptionProvider.java | 66 ++ .../androidtv/internal/AndroidTVHandler.java | 98 ++- .../internal/AndroidTVHandlerFactory.java | 16 +- .../protocol/philipstv/ConnectionManager.java | 129 ++++ .../philipstv/ConnectionManagerUtil.java | 98 +++ .../philipstv/PhilipsTVBindingConstants.java | 105 +++ .../philipstv/PhilipsTVConfiguration.java | 31 + .../philipstv/PhilipsTVConnectionManager.java | 696 ++++++++++++++++++ .../protocol/philipstv/WakeOnLanUtil.java | 136 ++++ .../PhilipsTVDiscoveryParticipant.java | 103 +++ .../philipstv/pairing/PhilipsTVPairing.java | 204 +++++ .../philipstv/pairing/model/AuthDTO.java | 77 ++ .../philipstv/pairing/model/DeviceDTO.java | 100 +++ .../pairing/model/FinishPairingDTO.java | 60 ++ .../philipstv/pairing/model/PairingDTO.java | 65 ++ .../pairing/model/RequestCodeDTO.java | 62 ++ .../philipstv/service/AmbilightService.java | 316 ++++++++ .../philipstv/service/AppService.java | 207 ++++++ .../protocol/philipstv/service/KeyPress.java | 100 +++ .../philipstv/service/KeyPressService.java | 107 +++ .../philipstv/service/PowerService.java | 116 +++ .../service/SearchContentService.java | 90 +++ .../philipstv/service/ServiceUtil.java | 57 ++ .../philipstv/service/TvChannelService.java | 147 ++++ .../philipstv/service/TvPictureService.java | 132 ++++ .../philipstv/service/VolumeService.java | 107 +++ .../service/api/PhilipsTVService.java | 61 ++ .../philipstv/service/model/DataDTO.java | 47 ++ .../philipstv/service/model/NodesDTO.java | 40 + .../service/model/TvSettingsCurrentDTO.java | 51 ++ .../service/model/TvSettingsUpdateDTO.java | 51 ++ .../philipstv/service/model/ValueDTO.java | 81 ++ .../philipstv/service/model/ValuesDTO.java | 47 ++ .../model/ambilight/AmbilightColorDTO.java | 75 ++ .../ambilight/AmbilightColorDeltaDTO.java | 64 ++ .../ambilight/AmbilightColorSettingsDTO.java | 69 ++ .../model/ambilight/AmbilightConfigDTO.java | 93 +++ .../model/ambilight/AmbilightLoungeDTO.java | 46 ++ .../model/ambilight/AmbilightModeDTO.java | 52 ++ .../model/ambilight/AmbilightPowerDTO.java | 45 ++ .../model/ambilight/AmbilightTopologyDTO.java | 124 ++++ .../model/application/ApplicationsDTO.java | 92 +++ .../model/application/AvailableAppsDTO.java | 59 ++ .../model/application/ComponentDTO.java | 52 ++ .../model/application/CurrentAppDTO.java | 48 ++ .../service/model/application/ExtrasDTO.java | 41 ++ .../service/model/application/IntentDTO.java | 72 ++ .../model/application/LaunchAppDTO.java | 49 ++ .../model/channel/AvailableTvChannelsDTO.java | 116 +++ .../service/model/channel/ChannelDTO.java | 135 ++++ .../service/model/channel/ChannelListDTO.java | 47 ++ .../service/model/channel/TvChannelDTO.java | 55 ++ .../service/model/keypress/KeyPressDTO.java | 46 ++ .../service/model/power/PowerStateDTO.java | 59 ++ .../service/model/volume/VolumeDTO.java | 50 ++ .../OH-INF/i18n/androidtv.properties | 110 ++- .../resources/OH-INF/thing/thing-types.xml | 233 ++++++ 62 files changed, 5795 insertions(+), 41 deletions(-) create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java diff --git a/bundles/org.openhab.binding.androidtv/NOTICE b/bundles/org.openhab.binding.androidtv/NOTICE index 38d625e349232..489fa7b4b6821 100644 --- a/bundles/org.openhab.binding.androidtv/NOTICE +++ b/bundles/org.openhab.binding.androidtv/NOTICE @@ -11,3 +11,21 @@ https://www.eclipse.org/legal/epl-2.0/. == Source Code https://github.com/openhab/openhab-addons + +== Third-party Content + +httpclient +* License: Apache 2.0 License +* Project: https://hc.apache.org/httpcomponents-client-ga +* Source: https://hc.apache.org/httpcomponents-client-ga + +httpcore +* License: Apache 2.0 License +* Project: http://hc.apache.org/httpcomponents-core-ga +* Source: http://hc.apache.org/httpcomponents-core-ga + +jackson +* License: Apache 2.0 License +* Project: https://github.com/FasterXML/jackson +* Source: https://github.com/FasterXML/jackson + diff --git a/bundles/org.openhab.binding.androidtv/README.md b/bundles/org.openhab.binding.androidtv/README.md index 1fce2128de78b..30c6660dc55ee 100644 --- a/bundles/org.openhab.binding.androidtv/README.md +++ b/bundles/org.openhab.binding.androidtv/README.md @@ -1,8 +1,9 @@ # AndroidTV Binding This binding is designed to emulate different protocols to interact with the AndroidTV platform. -Currently it emulates both the Google Video App to interact with a variety of AndroidTVs for purposes of remote control. +Currently it emulates the Google Video App to interact with a variety of AndroidTVs for purposes of remote control. It also currently emulates the Nvidia ShieldTV Android App to interact with an Nvidia ShieldTV for purposes of remote control. +It also currently emulates the PhilipsTV App to interact with a 2016+ PhilipsTV for purposes of remote control. ## Supported Things @@ -10,14 +11,15 @@ This binding supports two thing types: - **googletv** - An AndroidTV running Google Video - **shieldtv** - An Nvidia ShieldTV +- **philipstv** - A 2016+ Philips TV ## Discovery -Both GoogleTVs and ShieldTVs should be added automatically to the inbox through the mDNS discovery process. +All relevant thing types should be added automatically to the inbox through the mDNS discovery process. -In the case of the ShieldTV, openHAB will likely create an inbox entry for both a GoogleTV and a ShieldTV device. -Only the ShieldTV device should be configured, the GoogleTV can be ignored. -There is no benefit to configuring two things for a ShieldTV device. +In the case of the ShieldTV or PhilipsTV, openHAB will likely create an inbox entry for both a GoogleTV and a ShieldTV or PhilipsTV device. +Only the ShieldTV or PhilipsTV device should be configured, the GoogleTV can be ignored. +There is no benefit to configuring two things for a ShieldTV or PhilipsTV device. This could cause undesired effects. ## Binding Configuration @@ -30,37 +32,58 @@ This binding requires GoogleTV to be installed on the device (https://play.googl ## Thing Configuration -There are three required fields to connect successfully to a ShieldTV. +The is one required field to connect to the devices. All other fields are optional. | Name | Type | Description | Default | Required | Advanced | |------------------|---------|---------------------------------------|---------|----------|----------| | ipAddress | text | IP address of the device | N/A | yes | no | -| googletvPort | text | TCP Port for GoogleTV | 6466 | no | no | -| shieldtvPort | text | TCP Port for ShieldTV | 8987 | no | no | -| keystore | text | Location of the Java Keystore | N/A | no | no | -| keystorePassword | text | Password of the Java Keystore | N/A | no | no | -| gtvEnabled | boolean | Enable/Disable the GoogleTV protocol | true | no | no | +| googletvPort | text | TCP Port for GoogleTV | 6466 | no | yes | +| shieldtvPort | text | TCP Port for ShieldTV | 8987 | no | yes | +| philipstvPort | text | TCP Port for PhilipsTV | 1926 | no | yes | +| keystoreFileName | text | Location of the Java Keystore | N/A | no | yes | +| keystorePassword | text | Password of the Java Keystore | N/A | no | yes | +| reconnect | text | Delay between reconnections | 60 | no | yes | +| heartbeat | text | Frequency of heartbeats | 5 | no | yes | +| delay | text | Delay between messages | 0 | no | yes | +| refreshRate | text | Refresh interval of PhilipsTV | 10 | no | yes | +| useUpnpDiscovery | boolean | Enables UPnP Discovery for PhilipsTV | true | no | yes | +| gtvEnabled | boolean | Enable/Disable the GoogleTV protocol | true | no | yes | ```java Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ] Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ] +Thing androidtv:philipstv:bedroom [ ipAddress="192.168.1.4" ] ``` ## Channels -| Channel | Type | Description | GoogleTV | ShieldTV | -|------------|--------|-----------------------------|----------|----------| -| keyboard | String | Keyboard Data Entry | RW | RW | -| keypress | String | Manual Key Press Entry | RW | RW | -| keycode | String | Direct KEYCODE Entry | RW | RW | -| pincode | String | PIN Code Entry | RW | RW | -| app | String | App Control | RO | RW | -| appname | String | App Name | N/A | RW | -| appurl | String | App URL | N/A | RW | -| player | Player | Player Control | RW | RW | -| power | Switch | Power Control | RW | RW | -| volume | Dimmer | Volume Control | RO | RO | -| mute | Switch | Mute Control | RW | RW | +| Channel | Type | Description | GoogleTV | ShieldTV | PhilipsTV | +|----------------------|--------|--------------------------------------|----------|----------|-----------| +| keyboard | String | Keyboard Data Entry | RW | RW | RW | +| keypress | String | Manual Key Press Entry | RW | RW | RW | +| keycode | String | Direct KEYCODE Entry | RW | RW | RW | +| pincode | String | PIN Code Entry | RW | RW | RW | +| app | String | App Control | RO | RW | RW | +| appname | String | App Name | N/A | RW | RW | +| appurl | String | App URL | N/A | RO | N/A | +| appicon | Image | App Icon | N/A | N/A | RO | +| player | Player | Player Control | RW | RW | RW | +| power | Switch | Power Control | RW | RW | RW | +| volume | Dimmer | Volume Control | RO | RO | RW | +| mute | Switch | Mute Control | RW | RW | RW | +| tvChannel | String | TV Channel Control | N/A | N/A | RW | +| brightness | Dimmer | Brightness Control | N/A | N/A | RW | +| contrast | Dimmer | Contrast Control | N/A | N/A | RW | +| sharpness | Dimmer | Sharpness Control | N/A | N/A | RW | +| searchContent | String | Google Assistant search | N/A | N/A | RW | +| ambilightPower | Switch | Ambilight power control | N/A | N/A | RW | +| ambilightHuePower | Switch | Ambilight + Hue power control | N/A | N/A | RW | +| ambilightStyle | String | Ambilight Style plus algorithm used | N/A | N/A | RW | +| ambilightColor | Color | Color for all Ambilight Sides | N/A | N/A | RW | +| ambilightLeftColor | Color | Color for left Ambilight Side | N/A | N/A | RW | +| ambilightRightColor | Color | Color for right Ambilight Side | N/A | N/A | RW | +| ambilightTopColor | Color | Color for top Ambilight Side | N/A | N/A | RW | +| ambilightBottomColor | Color | Color for bottom Ambilight Side | N/A | N/A | RW | ```java @@ -76,6 +99,32 @@ Switch ShieldTV_POWER "POWER [%s]" { channel = "androidtv:shieldtv:livingroom:po Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" } Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" } +String PhilipsTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:philipstv:bedroom:keyboard" } +String PhilipsTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:philipstv:bedroom:keypress" } +String PhilipsTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:philipstv:bedroom:keycode" } +String PhilipsTV_PINCODE "PINCODE [%s]" { channel = "androidtv:philipstv:bedroom:pincode" } +String PhilipsTV_APP "APP [%s]" { channel = "androidtv:philipstv:bedroom:app" } +String PhilipsTV_APPNAME "APPNAME [%s]" { channel = "androidtv:philipstv:bedroom:appname" } +Image PhilipsTV_APPICON "APPICON [%s]" { channel = "androidtv:philipstv:bedroom:appicon" } +Player PhilipsTV_PLAYER "PLAYER [%s]" { channel = "androidtv:philipstv:bedroom:player" } +Switch PhilipsTV_POWER "POWER" { channel = "androidtv:philipstv:bedroom:power" } +Dimmer PhilipsTV_VOLUME "VOLUME [%s]" { channel = "androidtv:philipstv:bedroom:volume" } +Switch PhilipsTV_MUTE "MUTE [%s]" { channel = "androidtv:philipstv:bedroom:mute" } +String PhilipsTV_TVCHANNEL "TVCHANNEL [%s]" { channel = "androidtv:philipstv:bedroom:tvChannel" } +String PhilipsTV_SEARCHCONTENT "SEARCH CONTENT [%s]" { channel = "androidtv:philipstv:bedroom:searchContent" } +Switch PhilipsTV_AMBILIGHTPOWER "AMBILIGHT POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightPower" } +Switch PhilipsTV_AMBILIGHTHUEPOWER "AMBILIGHT HUE POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightHuePower" } +Switch PhilipsTV_AMBILIGHTLOUNGEPOWER "AMBILIGHT LOUNGE POWER" { channel = "androidtv:philipstv:bedroom:ambilightLoungePower" } +String PhilipsTV_AMBILIGHTSTYLE "AMBILIGHT STYLE" { channel = "androidtv:philipstv:bedroom:ambilightStyle" } +Color PhilipsTV_AMBILIGHTALLCOLOR "ALL SIDES AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightColor" } +Color PhilipsTV_AMBILIGHTLEFTCOLOR "LEFT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightLeftColor" } +Color PhilipsTV_AMBILIGHTRIGHTCOLOR "RIGHT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightRightColor" } +Color PhilipsTV_AMBILIGHTTOPCOLOR "TOP SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightTopColor" } +Color PhilipsTV_AMBILIGHTBOTTOMCOLOR "BOTTOM SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightBottomColor" } +Dimmer PhilipsTV_BRIGHTNESS "BRIGHTNESS [%s]" { channel = "androidtv:philipstv:bedroom:brightness" } +Dimmer PhilipsTV_CONTRAST "CONTRAST [%s]" { channel = "androidtv:philipstv:bedroom:contrast" } +Dimmer PhilipsTV_SHARPNESS "SHARPNESS [%s]" { channel = "androidtv:philipstv:bedroom:sharpness" } + String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" } String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" } String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" } @@ -168,7 +217,7 @@ openhab> openhab:androidtv androidtv:googletv:theater pincode abc123 The display should return back to where it was originally. -If you are on a ShieldTV you must run that process a second time to authenticate the GoogleTV protocol stack. +If you are on a ShieldTV or PhilipsTV you must run that process a second time to authenticate the GoogleTV protocol stack. This completes the PIN process. @@ -179,6 +228,7 @@ Upon reconnection (either from reconfiguration or a restart of OpenHAB), you sho ```java Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ] Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ] +Thing androidtv:philipstv:bedroom [ ipAddress="192.168.1.4" ] ``` ```java @@ -194,6 +244,32 @@ Switch ShieldTV_POWER "POWER [%s]" { channel = "androidtv:shieldtv:livingroom:po Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" } Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" } +String PhilipsTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:philipstv:bedroom:keyboard" } +String PhilipsTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:philipstv:bedroom:keypress" } +String PhilipsTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:philipstv:bedroom:keycode" } +String PhilipsTV_PINCODE "PINCODE [%s]" { channel = "androidtv:philipstv:bedroom:pincode" } +String PhilipsTV_APP "APP [%s]" { channel = "androidtv:philipstv:bedroom:app" } +String PhilipsTV_APPNAME "APPNAME [%s]" { channel = "androidtv:philipstv:bedroom:appname" } +Image PhilipsTV_APPICON "APPICON [%s]" { channel = "androidtv:philipstv:bedroom:appicon" } +Player PhilipsTV_PLAYER "PLAYER [%s]" { channel = "androidtv:philipstv:bedroom:player" } +Switch PhilipsTV_POWER "POWER" { channel = "androidtv:philipstv:bedroom:power" } +Dimmer PhilipsTV_VOLUME "VOLUME [%s]" { channel = "androidtv:philipstv:bedroom:volume" } +Switch PhilipsTV_MUTE "MUTE [%s]" { channel = "androidtv:philipstv:bedroom:mute" } +String PhilipsTV_TVCHANNEL "TVCHANNEL [%s]" { channel = "androidtv:philipstv:bedroom:tvChannel" } +String PhilipsTV_SEARCHCONTENT "SEARCH CONTENT [%s]" { channel = "androidtv:philipstv:bedroom:searchContent" } +Switch PhilipsTV_AMBILIGHTPOWER "AMBILIGHT POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightPower" } +Switch PhilipsTV_AMBILIGHTHUEPOWER "AMBILIGHT HUE POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightHuePower" } +Switch PhilipsTV_AMBILIGHTLOUNGEPOWER "AMBILIGHT LOUNGE POWER" { channel = "androidtv:philipstv:bedroom:ambilightLoungePower" } +String PhilipsTV_AMBILIGHTSTYLE "AMBILIGHT STYLE" { channel = "androidtv:philipstv:bedroom:ambilightStyle" } +Color PhilipsTV_AMBILIGHTALLCOLOR "ALL SIDES AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightColor" } +Color PhilipsTV_AMBILIGHTLEFTCOLOR "LEFT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightLeftColor" } +Color PhilipsTV_AMBILIGHTRIGHTCOLOR "RIGHT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightRightColor" } +Color PhilipsTV_AMBILIGHTTOPCOLOR "TOP SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightTopColor" } +Color PhilipsTV_AMBILIGHTBOTTOMCOLOR "BOTTOM SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightBottomColor" } +Dimmer PhilipsTV_BRIGHTNESS "BRIGHTNESS [%s]" { channel = "androidtv:philipstv:bedroom:brightness" } +Dimmer PhilipsTV_CONTRAST "CONTRAST [%s]" { channel = "androidtv:philipstv:bedroom:contrast" } +Dimmer PhilipsTV_SHARPNESS "SHARPNESS [%s]" { channel = "androidtv:philipstv:bedroom:sharpness" } + String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" } String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" } String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" } diff --git a/bundles/org.openhab.binding.androidtv/pom.xml b/bundles/org.openhab.binding.androidtv/pom.xml index 662c89ad21446..b4379e2613424 100644 --- a/bundles/org.openhab.binding.androidtv/pom.xml +++ b/bundles/org.openhab.binding.androidtv/pom.xml @@ -14,6 +14,10 @@ openHAB Add-ons :: Bundles :: AndroidTV Binding + + !net.sf.ehcache.*,!net.spy.* + + org.bouncycastle @@ -33,6 +37,36 @@ 1.75 compile + + org.apache.httpcomponents + httpclient-osgi + 4.5.14 + compile + + + org.apache.httpcomponents + httpcore-osgi + 4.4.16 + compile + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + compile + diff --git a/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml b/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml index 4704774878262..1249665cd8c92 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml @@ -4,6 +4,7 @@ openhab-runtime-base + openhab-transport-upnp mvn:org.openhab.addons.bundles/org.openhab.binding.androidtv/${project.version} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java index 776122e5f8669..e5ad0e34dbd6b 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java @@ -31,8 +31,9 @@ public class AndroidTVBindingConstants { // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_GOOGLETV = new ThingTypeUID(BINDING_ID, "googletv"); public static final ThingTypeUID THING_TYPE_SHIELDTV = new ThingTypeUID(BINDING_ID, "shieldtv"); - - public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV); + public static final ThingTypeUID THING_TYPE_PHILIPSTV = new ThingTypeUID(BINDING_ID, "philipstv"); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV, + THING_TYPE_PHILIPSTV); // List of all Channel ids public static final String CHANNEL_DEBUG = "debug"; @@ -43,15 +44,32 @@ public class AndroidTVBindingConstants { public static final String CHANNEL_APP = "app"; public static final String CHANNEL_APPNAME = "appname"; public static final String CHANNEL_APPURL = "appurl"; + public static final String CHANNEL_APP_ICON = "appicon"; public static final String CHANNEL_POWER = "power"; public static final String CHANNEL_VOLUME = "volume"; public static final String CHANNEL_MUTE = "mute"; public static final String CHANNEL_PLAYER = "player"; + public static final String CHANNEL_BRIGHTNESS = "brightness"; + public static final String CHANNEL_CONTRAST = "contrast"; + public static final String CHANNEL_SHARPNESS = "sharpness"; + public static final String CHANNEL_TV_CHANNEL = "tvChannel"; + public static final String CHANNEL_SEARCH_CONTENT = "searchContent"; + public static final String CHANNEL_AMBILIGHT = "ambilight"; + public static final String CHANNEL_AMBILIGHT_POWER = "ambilightPower"; + public static final String CHANNEL_AMBILIGHT_HUE_POWER = "ambilightHuePower"; + public static final String CHANNEL_AMBILIGHT_LOUNGE_POWER = "ambilightLoungePower"; + public static final String CHANNEL_AMBILIGHT_STYLE = "ambilightStyle"; + public static final String CHANNEL_AMBILIGHT_COLOR = "ambilightColor"; + public static final String CHANNEL_AMBILIGHT_LEFT_COLOR = "ambilightLeftColor"; + public static final String CHANNEL_AMBILIGHT_RIGHT_COLOR = "ambilightRightColor"; + public static final String CHANNEL_AMBILIGHT_TOP_COLOR = "ambilightTopColor"; + public static final String CHANNEL_AMBILIGHT_BOTTOM_COLOR = "ambilightBottomColor"; // List of all config properties public static final String PARAMETER_IP_ADDRESS = "ipAddress"; public static final String PARAMETER_GOOGLETV_PORT = "googletvPort"; public static final String PARAMETER_SHIELDTV_PORT = "shieldtvPort"; + public static final String PARAMETER_PHILIPSTV_PORT = "philipstvPort"; public static final String PARAMETER_GTV_ENABLED = "gtvEnabled"; // List of all static String literals diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java new file mode 100644 index 0000000000000..3cf1557891cea --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.type.DynamicStateDescriptionProvider; +import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragmentBuilder; +import org.openhab.core.types.StateOption; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; + +/** + * Dynamic provider of state options while leaving other state description fields as original. + * + * @author Benjamin Meyer - Initial contribution + */ +@Component(service = { DynamicStateDescriptionProvider.class, + AndroidTVDynamicStateDescriptionProvider.class }, immediate = true) +@NonNullByDefault +public class AndroidTVDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider { + private final Map> channelOptionsMap = new ConcurrentHashMap<>(); + + public void setStateOptions(ChannelUID channelUID, List options) { + channelOptionsMap.put(channelUID, options); + } + + @Override + public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original, + @Nullable Locale locale) { + List options = channelOptionsMap.get(channel.getUID()); + + if (options == null) { + return null; + } + if (original != null) { + return StateDescriptionFragmentBuilder.create(original).withOptions(options).build().toStateDescription(); + } + + return StateDescriptionFragmentBuilder.create().withOptions(options).build().toStateDescription(); + } + + @Deactivate + public void deactivate() { + channelOptionsMap.clear(); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java index dd2660b3cc123..c29aba647fe42 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java @@ -25,14 +25,19 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConfiguration; import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConfiguration; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConfiguration; import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConnectionManager; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.config.discovery.DiscoveryServiceRegistry; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; import org.openhab.core.types.CommandOption; @@ -55,6 +60,7 @@ public class AndroidTVHandler extends BaseThingHandler { private @Nullable ShieldTVConnectionManager shieldtvConnectionManager; private @Nullable GoogleTVConnectionManager googletvConnectionManager; + private @Nullable PhilipsTVConnectionManager philipstvConnectionManager; private @Nullable ScheduledFuture monitorThingStatusJob; private final Object monitorThingStatusJobLock = new Object(); @@ -68,13 +74,20 @@ public class AndroidTVHandler extends BaseThingHandler { private String currentThingStatus = ""; private boolean currentThingFailed = false; + private DiscoveryServiceRegistry discoveryServiceRegistry; + + private AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider; + public AndroidTVHandler(Thing thing, AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider, - AndroidTVTranslationProvider translationProvider, ThingTypeUID thingTypeUID) { + AndroidTVTranslationProvider translationProvider, DiscoveryServiceRegistry discoveryServiceRegistry, + AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider, ThingTypeUID thingTypeUID) { super(thing); this.commandDescriptionProvider = commandDescriptionProvider; this.translationProvider = translationProvider; this.thingTypeUID = thingTypeUID; this.thingID = this.getThing().getUID().getId(); + this.discoveryServiceRegistry = discoveryServiceRegistry; + this.stateDescriptionProvider = stateDescriptionProvider; } public void setThingProperty(String property, String value) { @@ -89,6 +102,22 @@ public String getThingID() { return this.thingID; } + public Configuration getThingConfig() { + return getConfig(); + } + + public DiscoveryServiceRegistry getDiscoveryServiceRegistry() { + return discoveryServiceRegistry; + } + + public AndroidTVDynamicStateDescriptionProvider getStateDescriptionProvider() { + return stateDescriptionProvider; + } + + public ThingUID getThingUID() { + return getThing().getUID(); + } + public void updateChannelState(String channel, State state) { updateState(channel, state); } @@ -122,6 +151,7 @@ public void checkThingStatus() { GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (googletvConnectionManager != null) { if (!googletvConnectionManager.getLoggedIn()) { @@ -143,6 +173,15 @@ public void checkThingStatus() { } } + if (THING_TYPE_PHILIPSTV.equals(thingTypeUID)) { + if (philipstvConnectionManager != null) { + if (!philipstvConnectionManager.getLoggedIn()) { + failed = true; + } + statusMessage = statusMessage + "PhilipsTV: " + philipstvConnectionManager.getStatusMessage(); + } + } + if (!currentThingStatus.equals(statusMessage) || (currentThingFailed != failed)) { if (failed) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, statusMessage); @@ -186,14 +225,30 @@ public void initialize() { shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig); } + if (THING_TYPE_PHILIPSTV.equals(thingTypeUID)) { + PhilipsTVConfiguration philipstvConfig = getConfigAs(PhilipsTVConfiguration.class); + ipAddress = philipstvConfig.ipAddress; + + if (ipAddress.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.philipstv-address-not-specified"); + return; + } + + philipstvConnectionManager = new PhilipsTVConnectionManager(this, philipstvConfig); + } + monitorThingStatusJob = scheduler.schedule(this::monitorThingStatus, THING_STATUS_FREQUENCY, TimeUnit.MILLISECONDS); } public void sendCommandToProtocol(ChannelUID channelUID, Command command) { ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (THING_TYPE_SHIELDTV.equals(thingTypeUID) && (shieldtvConnectionManager != null)) { shieldtvConnectionManager.handleCommand(channelUID, command); + } else if (THING_TYPE_PHILIPSTV.equals(thingTypeUID) && (philipstvConnectionManager != null)) { + philipstvConnectionManager.handleCommand(channelUID, command); } } @@ -208,6 +263,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (CHANNEL_DEBUG.equals(channelUID.getId())) { if (command instanceof StringType) { @@ -231,10 +287,18 @@ public void handleCommand(ChannelUID channelUID, Command command) { ShieldTVConfiguration shieldtvConfig = getConfigAs(ShieldTVConfiguration.class); shieldtvConfig.shim = true; shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig); + } else if (command.toString().equals("PHILIPSTV_HALT") && (philipstvConnectionManager != null)) { + philipstvConnectionManager.dispose(); + philipstvConnectionManager = null; + } else if (command.toString().equals("PHILIPSTV_START")) { + PhilipsTVConfiguration philipstvConfig = getConfigAs(PhilipsTVConfiguration.class); + philipstvConnectionManager = new PhilipsTVConnectionManager(this, philipstvConfig); } else if (command.toString().startsWith("GOOGLETV") && (googletvConnectionManager != null)) { googletvConnectionManager.handleCommand(channelUID, command); } else if (command.toString().startsWith("SHIELDTV") && (shieldtvConnectionManager != null)) { shieldtvConnectionManager.handleCommand(channelUID, command); + } else if (command.toString().startsWith("PHILIPSTV") && (philipstvConnectionManager != null)) { + philipstvConnectionManager.handleCommand(channelUID, command); } } return; @@ -259,6 +323,33 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } + if (THING_TYPE_PHILIPSTV.equals(thingTypeUID) && (philipstvConnectionManager != null)) { + if (googletvConnectionManager != null) { + if (CHANNEL_PINCODE.equals(channelUID.getId())) { + if (command instanceof StringType) { + if (!philipstvConnectionManager.getLoggedIn()) { + philipstvConnectionManager.handleCommand(channelUID, command); + return; + } + } + } else if (CHANNEL_POWER.equals(channelUID.getId()) && !googletvConnectionManager.getLoggedIn()) { + philipstvConnectionManager.handleCommand(channelUID, command); + return; + } else if (CHANNEL_APP.equals(channelUID.getId()) || CHANNEL_APPNAME.equals(channelUID.getId()) + || CHANNEL_TV_CHANNEL.equals(channelUID.getId()) || CHANNEL_VOLUME.equals(channelUID.getId()) + || CHANNEL_MUTE.equals(channelUID.getId()) || CHANNEL_SEARCH_CONTENT.equals(channelUID.getId()) + || CHANNEL_BRIGHTNESS.equals(channelUID.getId()) || CHANNEL_SHARPNESS.equals(channelUID.getId()) + || CHANNEL_CONTRAST.equals(channelUID.getId()) + || channelUID.getId().startsWith(CHANNEL_AMBILIGHT)) { + philipstvConnectionManager.handleCommand(channelUID, command); + return; + } + } else { + philipstvConnectionManager.handleCommand(channelUID, command); + return; + } + } + if (googletvConnectionManager != null) { googletvConnectionManager.handleCommand(channelUID, command); return; @@ -279,11 +370,16 @@ public void dispose() { GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (shieldtvConnectionManager != null) { shieldtvConnectionManager.dispose(); } + if (philipstvConnectionManager != null) { + philipstvConnectionManager.dispose(); + } + if (googletvConnectionManager != null) { googletvConnectionManager.dispose(); } diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java index 4b2803b3ec649..6abf7346c9f2d 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.discovery.DiscoveryServiceRegistry; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.thing.Thing; @@ -39,18 +40,24 @@ @Component(configurationPid = "binding.androidtv", service = ThingHandlerFactory.class) public class AndroidTVHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV, - THING_TYPE_SHIELDTV); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV, + THING_TYPE_PHILIPSTV); private final AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider; private final AndroidTVTranslationProvider translationProvider; + private final DiscoveryServiceRegistry discoveryServiceRegistry; + private final AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider; @Activate public AndroidTVHandlerFactory( final @Reference AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider, - final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider) { + final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider, + final @Reference DiscoveryServiceRegistry discoveryServiceRegistry, + final @Reference AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider) { this.commandDescriptionProvider = commandDescriptionProvider; this.translationProvider = new AndroidTVTranslationProvider(i18nProvider, localeProvider); + this.discoveryServiceRegistry = discoveryServiceRegistry; + this.stateDescriptionProvider = stateDescriptionProvider; } @Override @@ -61,6 +68,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - return new AndroidTVHandler(thing, commandDescriptionProvider, translationProvider, thingTypeUID); + return new AndroidTVHandler(thing, commandDescriptionProvider, translationProvider, discoveryServiceRegistry, + stateDescriptionProvider, thingTypeUID); } } diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java new file mode 100644 index 0000000000000..f282666fdf0c5 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpResponseException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link ConnectionManager} is responsible for handling https GETs and POSTs to the Philips + * TVs. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class ConnectionManager { + + private static final String TARGET_URI_MSG = "Target Uri is: {}"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + // Cannot use jetty in OH2.4 due to 9.4.11.v20180605 version with digest auth bug + // https://github.com/eclipse/jetty.project/issues/1555 + private final CloseableHttpClient httpClient; + + private final HttpHost httpHost; + + public ConnectionManager(CloseableHttpClient httpClient, HttpHost httpHost) { + this.httpClient = httpClient; + this.httpHost = httpHost; + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public String doHttpsGet(String path) throws IOException { + String uri = httpHost.toURI() + path; + logger.debug(TARGET_URI_MSG, uri); + HttpGet httpGet = new HttpGet(uri); + String jsonContent = ""; + try (CloseableHttpClient client = httpClient; // + CloseableHttpResponse response = client.execute(httpHost, httpGet)) { + validateResponse(response, uri); + jsonContent = getJsonFromResponse(response); + } catch (HttpHostConnectException e) { + logger.debug("HttpHostConnectException when getting {}", uri); + } + return jsonContent; + } + + public String doHttpsPost(String path, String json) throws IOException { + String uri = httpHost.toURI() + path; + logger.debug(TARGET_URI_MSG, uri); + HttpPost httpPost = new HttpPost(uri); + httpPost.setHeader("Content-type", "application/json"); + httpPost.setEntity(new StringEntity(json)); + String jsonContent = ""; + try (CloseableHttpClient client = httpClient; // + CloseableHttpResponse response = client.execute(httpHost, httpPost)) { + validateResponse(response, uri); + jsonContent = getJsonFromResponse(response); + } catch (HttpHostConnectException e) { + logger.debug("HttpHostConnectException when getting {}", uri); + } + return jsonContent; + } + + private void validateResponse(CloseableHttpResponse response, String uri) throws HttpResponseException { + if (response == null) { + throw new HttpResponseException(0, String.format("The response for the request to %s was empty.", uri)); + } else if (response.getStatusLine().getStatusCode() == 401) { + throw new HttpResponseException(401, "The given username/password combination is invalid."); + } + } + + private String getJsonFromResponse(HttpResponse response) throws IOException { + String jsonContent = EntityUtils.toString(response.getEntity()); + logger.trace("----------------------------------------"); + logger.trace("{}", response.getStatusLine()); + logger.trace("{}", jsonContent); + return jsonContent; + } + + public byte[] doHttpsGetForImage(String path) throws IOException { + String uri = httpHost.toURI() + path; + logger.debug(TARGET_URI_MSG, uri); + HttpGet httpGet = new HttpGet(uri); + try (CloseableHttpClient client = httpClient; + CloseableHttpResponse response = client.execute(httpHost, httpGet)) { + if ((response != null) && (response.getStatusLine().getStatusCode() == 401)) { + throw new HttpResponseException(401, "The given username/password combination is invalid."); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (response != null) { + response.getEntity().writeTo(baos); + } + return baos.toByteArray(); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java new file mode 100644 index 0000000000000..49a31e7186c39 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.CONNECT_TIMEOUT_MILLISECONDS; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.HTTPS; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.MAX_REQUEST_RETRIES; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.SOCKET_TIMEOUT_MILLISECONDS; + +import java.net.NoRouteToHostException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Optional; + +import javax.net.ssl.SSLContext; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ConnectionManagerUtil} is offering methods for connection specific processes. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public final class ConnectionManagerUtil { + + private ConnectionManagerUtil() { + } + + public static CloseableHttpClient createSharedHttpClient(HttpHost target, String username, String password) + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + CredentialsProvider credProvider = new BasicCredentialsProvider(); + credProvider.setCredentials(new AuthScope(target.getHostName(), target.getPort()), + new UsernamePasswordCredentials(username, password)); + + RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT_MILLISECONDS) + .setSocketTimeout(SOCKET_TIMEOUT_MILLISECONDS).build(); + + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(getSslConnectionWithoutCertValidation(), + NoopHostnameVerifier.INSTANCE); + + Registry socketFactoryRegistry = RegistryBuilder. create() + .register(HTTPS, sslsf).build(); + + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + + HttpRequestRetryHandler requestRetryHandler = (exception, executionCount, context) -> { + if (exception instanceof NoRouteToHostException) { + return false; + } + String message = Optional.ofNullable(exception.getMessage()).orElse(""); + if (!message.isEmpty()) { + if ((exception instanceof HttpHostConnectException) && message.contains("Connection refused")) { + return false; + } + } + return executionCount < MAX_REQUEST_RETRIES; + }; + + return HttpClients.custom().setDefaultRequestConfig(requestConfig).setSSLSocketFactory(sslsf) + .setDefaultCredentialsProvider(credProvider).setConnectionManager(connManager) + .setRetryHandler(requestRetryHandler).setConnectionManagerShared(true).build(); + } + + private static SSLContext getSslConnectionWithoutCertValidation() + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + return new SSLContextBuilder().loadTrustMaterial(null, (certificate, authType) -> true).build(); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java new file mode 100644 index 0000000000000..d57bb924d0fb7 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PhilipsTVBindingConstants} class defines common constants, which are used across the + * whole binding. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class PhilipsTVBindingConstants { + + // Config Parameters + public static final String HOST = "host"; + + public static final String PORT = "philipstvPort"; + + public static final String MAC_ADDRESS = "macAddress"; + + public static final String USERNAME = "username"; + + public static final String PASSWORD = "password"; + + public static final String HTTPS = "https"; + + // Connection specific values + static final int CONNECT_TIMEOUT_MILLISECONDS = 3 * 1000; + + static final int SOCKET_TIMEOUT_MILLISECONDS = 1000; + + static final int MAX_REQUEST_RETRIES = 3; + + // Default port for jointspace v6 + public static final int DEFAULT_PORT = 1926; + + // Powerstates + public static final String POWER_ON = "On"; + + public static final String POWER_OFF = "Off"; + + public static final String STANDBY = "Standby"; + + public static final String STANDBYKEEP = "StandbyKeep"; + + public static final String STANDBY_MSG = "online.standby"; + + public static final String EMPTY = ""; + + // REST Paths + public static final String SLASH = "/"; + + private static final String API_VERSION = "6"; + + public static final String BASE_PATH = SLASH + API_VERSION + SLASH; + + public static final String VOLUME_PATH = BASE_PATH + "audio" + SLASH + "volume"; + + public static final String KEY_CODE_PATH = BASE_PATH + "input" + SLASH + "key"; + + public static final String TV_POWERSTATE_PATH = BASE_PATH + "powerstate"; + + public static final String GET_AVAILABLE_APP_LIST_PATH = BASE_PATH + "applications"; + + public static final String GET_NETWORK_DEVICES_PATH = BASE_PATH + "network" + SLASH + "devices"; + + private static final String ACTIVITIES_BASE_PATH = BASE_PATH + "activities" + SLASH; + + public static final String GET_AVAILABLE_TV_CHANNEL_LIST_PATH = BASE_PATH + "channeldb" + SLASH + "tv" + SLASH + + "channelLists" + SLASH + "all"; + + public static final String TV_CHANNEL_PATH = ACTIVITIES_BASE_PATH + "tv"; + public static final String GET_CURRENT_APP_PATH = ACTIVITIES_BASE_PATH + "current"; + public static final String LAUNCH_APP_PATH = ACTIVITIES_BASE_PATH + "launch"; + + private static final String AMBILIGHT_BASE_PATH = BASE_PATH + "ambilight" + SLASH; + public static final String AMBILIGHT_POWERSTATE_PATH = AMBILIGHT_BASE_PATH + "power"; + public static final String AMBILIGHT_CONFIG_PATH = AMBILIGHT_BASE_PATH + "currentconfiguration"; + public static final String AMBILIGHT_MODE_PATH = AMBILIGHT_BASE_PATH + "mode"; + public static final String AMBILIGHT_CACHED_PATH = AMBILIGHT_BASE_PATH + "cached"; + public static final String AMBILIGHT_TOPOLOGY_PATH = AMBILIGHT_BASE_PATH + "topology"; + public static final String AMBILIGHT_LOUNGE_PATH = AMBILIGHT_BASE_PATH + "lounge"; + + private static final String SETTINGS_BASE_PATH = BASE_PATH + "menuitems" + SLASH + "settings" + SLASH; + public static final String UPDATE_SETTINGS_PATH = SETTINGS_BASE_PATH + "update"; + public static final String CURRENT_SETTINGS_PATH = SETTINGS_BASE_PATH + "current"; + public static final String STRUCTURE_SETTINGS_PATH = SETTINGS_BASE_PATH + "structure"; + + // Logging messages + public static final String TV_OFFLINE_MSG = "offline.tv-is-not-reachable-and-should-therefore-be-off"; + public static final String TV_NOT_LISTENING_MSG = "offline.tv-does-not-accept-commands-at-the-moment"; +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java new file mode 100644 index 0000000000000..e29bad8434531 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PhilipsTVConfiguration} class contains fields for mapping thing configuration parameters. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class PhilipsTVConfiguration { + + public String ipAddress = ""; + public Integer philipstvPort = 1926; + public Integer refreshRate = 10; + public boolean useUpnpDiscovery = true; + public String pairingCode = ""; +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java new file mode 100644 index 0000000000000..30c49311eacce --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java @@ -0,0 +1,696 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv; + +import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.NoRouteToHostException; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketTimeoutException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; + +import org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.androidtv.internal.AndroidTVDynamicStateDescriptionProvider; +import org.openhab.binding.androidtv.internal.AndroidTVHandler; +import org.openhab.binding.androidtv.internal.AndroidTVTranslationProvider; +import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.PhilipsTVPairing; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.AmbilightService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.AppService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.KeyPressService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.PowerService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.SearchContentService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.TvChannelService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.TvPictureService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.VolumeService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.core.OpenHAB; +import org.openhab.core.config.discovery.DiscoveryListener; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.config.discovery.DiscoveryServiceRegistry; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link PhilipsTVHandler} is responsible for handling commands, which are sent to one of the + * channels. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class PhilipsTVConnectionManager implements DiscoveryListener { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private AndroidTVHandler handler; + + public PhilipsTVConfiguration config; + + private ScheduledExecutorService scheduler; + + private final AndroidTVTranslationProvider translationProvider; + + private DiscoveryServiceRegistry discoveryServiceRegistry; + + private AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider; + + private @Nullable ThingUID upnpThingUID; + + private @Nullable ScheduledFuture refreshScheduler; + + private final Predicate> isRefreshSchedulerRunning = r -> (r != null) && !r.isCancelled(); + + private final ReentrantLock lock = new ReentrantLock(); + + private boolean isLoggedIn = false; + + private String statusMessage = ""; + + private HttpHost target; + + private String username = ""; + private String password = ""; + private String macAddress = ""; + + private @Nullable ScheduledFuture deviceHealthJob; + private boolean isOnline = true; + private boolean pendingPowerOn = false; + + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /* Philips TV services */ + private Map channelServices = new HashMap<>(); + + public PhilipsTVConnectionManager(AndroidTVHandler handler, PhilipsTVConfiguration config) { + logger.debug("Create a Philips TV Handler for thing '{}'", handler.getThingUID()); + this.handler = handler; + this.config = config; + this.scheduler = handler.getScheduler(); + this.translationProvider = handler.getTranslationProvider(); + this.discoveryServiceRegistry = handler.getDiscoveryServiceRegistry(); + this.stateDescriptionProvider = handler.getStateDescriptionProvider(); + this.target = new HttpHost(config.ipAddress, config.philipstvPort, HTTPS); + initialize(); + } + + private void setStatus(boolean isLoggedIn) { + if (isLoggedIn) { + setStatus(isLoggedIn, "online.online"); + } else { + setStatus(isLoggedIn, "offline.unknown"); + } + } + + private void setStatus(boolean isLoggedIn, String statusMessage) { + String translatedMessage = translationProvider.getText(statusMessage); + logger.trace("setStatus to {} {} {}", isLoggedIn, statusMessage, translatedMessage); + if ((this.isLoggedIn != isLoggedIn) || (!this.statusMessage.equals(translatedMessage))) { + this.isLoggedIn = isLoggedIn; + this.statusMessage = translatedMessage; + handler.checkThingStatus(); + } + } + + public String getStatusMessage() { + return statusMessage; + } + + public void setLoggedIn(boolean isLoggedIn) { + if (this.isLoggedIn != isLoggedIn) { + setStatus(isLoggedIn); + } + } + + public boolean getLoggedIn() { + return isLoggedIn; + } + + public void updateStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, String thingStatusMessage) { + if (thingStatus == ThingStatus.ONLINE) { + setLoggedIn(true); + } else { + logger.trace("Updating status to {} {} {}", thingStatus, thingStatusDetail, thingStatusMessage); + setStatus(false, thingStatusMessage); + } + } + + public String getMacAddress() { + return this.macAddress; + } + + public void saveConfigs() { + String folderName = OpenHAB.getUserDataFolder() + "/androidtv"; + File folder = new File(folderName); + + if (!folder.exists()) { + logger.debug("Creating directory {}", folderName); + folder.mkdirs(); + } + + String fileName = folderName + "/philipstv." + handler.getThing().getUID().getId() + ".config"; + + Map configMap = new HashMap<>(); + configMap.put("username", username); + configMap.put("password", password); + configMap.put("macAddress", macAddress); + + try { + String configJson = OBJECT_MAPPER.writeValueAsString(configMap); + logger.debug("Writing configJson \"{}\" to {}", configJson, fileName); + Files.write(Paths.get(fileName), configJson.getBytes()); + } catch (JsonProcessingException e) { + logger.warn("JsonProcessingException trying to save configMap: {}", e.getMessage(), e); + } catch (IOException ex) { + logger.debug("IOException when writing configJson to file {}", ex.getMessage()); + } + } + + private void readConfigs() { + String folderName = OpenHAB.getUserDataFolder() + "/androidtv"; + String fileName = folderName + "/philipstv." + handler.getThing().getUID().getId() + ".config"; + File file = new File(fileName); + if (!file.exists()) { + return; + } + try { + final byte[] contents = Files.readAllBytes(Paths.get(fileName)); + String configJson = new String(contents); + logger.debug("Read configJson \"{}\" from {}", configJson, fileName); + Map configMap = OBJECT_MAPPER.readValue(configJson, + new TypeReference>() { + }); + this.username = Optional.ofNullable(configMap.get("username")).orElse(""); + this.password = Optional.ofNullable(configMap.get("password")).orElse(""); + this.macAddress = Optional.ofNullable(configMap.get("macAddress")).orElse(""); + logger.debug("Processed configJson as {} {} {}", this.username, this.password, this.macAddress); + } catch (IOException ex) { + logger.debug("IOException when reading configJson from file {}", ex.getMessage()); + } + } + + public void setCreds(String username, String password) { + this.username = username; + this.password = password; + saveConfigs(); + } + + private boolean servicePing() { + int timeout = 500; + + SocketAddress socketAddress = new InetSocketAddress(config.ipAddress, config.philipstvPort); + try (Socket socket = new Socket()) { + socket.connect(socketAddress, timeout); + return true; + } catch (ConnectException | SocketTimeoutException | NoRouteToHostException ignored) { + return false; + } catch (IOException ignored) { + // IOException is thrown by automatic close() of the socket. + // This should actually never return a value as we should return true above already + return true; + } + } + + private void checkHealth() { + boolean isOnline = servicePing(); + logger.debug("{} - Device Health - Online: {} - Logged In: {}", handler.getThingID(), isOnline, isLoggedIn); + if (isOnline != this.isOnline) { + this.isOnline = isOnline; + if (isOnline) { + logger.debug("{} - Device is back online. Attempting reconnection.", handler.getThingID()); + connect(); + } else { + logger.debug("{} - Device is offline.", handler.getThingID()); + postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "offline.communication-error-will-try-to-reconnect"); + } + } + } + + public void checkPendingPowerOn() { + if (pendingPowerOn) { + @Nullable + PhilipsTVService powerService = channelServices.get(CHANNEL_POWER); + if (powerService != null) { + powerService.handleCommand(CHANNEL_POWER, OnOffType.ON); + } + pendingPowerOn = false; + startDeviceHealthJob(5, TimeUnit.SECONDS); + } + } + + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Received channel: {}, command: {}", channelUID, command); + String username = this.username; + String password = this.password; + + if (channelUID.getId().equals(CHANNEL_PINCODE)) { + if (command instanceof StringType) { + HttpHost target = new HttpHost(config.ipAddress, config.philipstvPort, HTTPS); + if (command.toString().equals("REQUEST")) { + try { + initPairingCodeRetrieval(target); + } catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "offline.error-occured-while-presenting-pairing-code"); + } + } else { + boolean hasFailed = initCredentialsRetrieval(target, command.toString()); + if (hasFailed) { + postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "offline.error-occured-during-retrieval-of-credentials"); + return; + } + readConfigs(); + username = this.username; + password = this.password; + + if ((username.isEmpty()) || (password.isEmpty())) { + postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "offline.pairing-was-unsuccessful"); + return; + } + + } + } + return; + } + + if ((username.isEmpty()) || (password.isEmpty())) { + return; // pairing process is not finished + } + + boolean isLoggedIn = this.isLoggedIn; + Map channelServices = this.channelServices; + + if ((!isLoggedIn) && (!channelUID.getId().equals(CHANNEL_POWER) + & !channelUID.getId().equals(CHANNEL_AMBILIGHT_LOUNGE_POWER))) { + // Check if tv turned on meanwhile + @Nullable + PhilipsTVService powerService = channelServices.get(CHANNEL_POWER); + if (powerService != null) { + powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH); + } + isLoggedIn = this.isLoggedIn; + if (!isLoggedIn) { + // still offline + logger.warn( + "Cannot execute command {} for channel {}: PowerState of TV was checked and resolved to offline.", + command, channelUID.getId()); + return; + } + } + + String channel = channelUID.getId(); + long startTime = System.currentTimeMillis(); + // Delegate the other commands to correct channel service + @Nullable + PhilipsTVService philipsTvService = channelServices.get(channel); + + if (philipsTvService == null) { + logger.warn("Unknown channel for Philips TV Binding: {}", channel); + return; + } + + if ((!isLoggedIn) && (channelUID.getId().equals(CHANNEL_POWER)) && (command.equals(OnOffType.ON))) { + startDeviceHealthJob(1, TimeUnit.SECONDS); + pendingPowerOn = true; + } + + philipsTvService.handleCommand(channel, command); + long stopTime = System.currentTimeMillis(); + long elapsedTime = stopTime - startTime; + logger.trace("The command {} took : {} nanoseconds", command.toFullString(), elapsedTime); + } + + public void initialize() { + logger.debug("Init of handler for Thing: {}", handler.getThingID()); + + readConfigs(); + String username = this.username; + String password = this.password; + + if ((username.isEmpty()) || (password.isEmpty())) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "offline.pairing-is-not-configured-yet"); + return; + } + + connect(); + startDeviceHealthJob(5, TimeUnit.SECONDS); + } + + private void startDeviceHealthJob(int interval, TimeUnit unit) { + ScheduledFuture deviceHealthJob = this.deviceHealthJob; + if (deviceHealthJob != null) { + deviceHealthJob.cancel(true); + } + this.deviceHealthJob = scheduler.scheduleWithFixedDelay(this::checkHealth, interval, interval, unit); + } + + private void connect() { + HttpHost target = this.target; + String username = this.username; + String password = this.password; + String macAddress = this.macAddress; + logger.debug("Starting connection to {} {} {}", username, password, macAddress); + + if (!config.useUpnpDiscovery && isSchedulerInitializable()) { + logger.debug("connect starting refresh scheduler"); + startRefreshScheduler(); + } + + CloseableHttpClient httpClient; + + try { + httpClient = ConnectionManagerUtil.createSharedHttpClient(target, username, password); + } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + String.format("offline.error-occurred-during-creation-of-http-client: %s", e.getMessage())); + return; + } + + ConnectionManager connectionManager = new ConnectionManager(httpClient, target); + + if (macAddress.isEmpty()) { + try { + Optional wolAddress = WakeOnLanUtil.getMacFromEnabledInterface(connectionManager); + if (wolAddress.isPresent()) { + this.macAddress = wolAddress.get(); + saveConfigs(); + } else { + logger.debug("MAC Address could not be determined for Wake-On-LAN support, " + + "because Wake-On-LAN is not enabled on the TV."); + } + } catch (IOException e) { + logger.debug("Error occurred during retrieval of MAC Address: {}", e.getMessage()); + } + } + + startServices(connectionManager); + + discoveryServiceRegistry.addDiscoveryListener(this); + + // Thing is initialized, check power state and available communication of the TV and set ONLINE or OFFLINE + postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.online"); + + Map channelServices = this.channelServices; + @Nullable + PhilipsTVService powerService = channelServices.get(CHANNEL_POWER); + if (powerService != null) { + powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH); + } + } + + private void startServices(ConnectionManager connectionManager) { + Map services = new HashMap<>(); + + PhilipsTVService volumeService = new VolumeService(this, connectionManager); + services.put(CHANNEL_VOLUME, volumeService); + services.put(CHANNEL_MUTE, volumeService); + + PhilipsTVService tvPictureService = new TvPictureService(this, connectionManager); + services.put(CHANNEL_BRIGHTNESS, tvPictureService); + services.put(CHANNEL_SHARPNESS, tvPictureService); + services.put(CHANNEL_CONTRAST, tvPictureService); + + PhilipsTVService keyPressService = new KeyPressService(this, connectionManager); + services.put(CHANNEL_KEYPRESS, keyPressService); + services.put(CHANNEL_PLAYER, keyPressService); + + PhilipsTVService appService = new AppService(this, connectionManager); + services.put(CHANNEL_APP, appService); + services.put(CHANNEL_APPNAME, appService); + services.put(CHANNEL_APP_ICON, appService); + + PhilipsTVService ambilightService = new AmbilightService(this, connectionManager); + services.put(CHANNEL_AMBILIGHT_POWER, ambilightService); + services.put(CHANNEL_AMBILIGHT_HUE_POWER, ambilightService); + services.put(CHANNEL_AMBILIGHT_LOUNGE_POWER, ambilightService); + services.put(CHANNEL_AMBILIGHT_STYLE, ambilightService); + services.put(CHANNEL_AMBILIGHT_COLOR, ambilightService); + services.put(CHANNEL_AMBILIGHT_LEFT_COLOR, ambilightService); + services.put(CHANNEL_AMBILIGHT_RIGHT_COLOR, ambilightService); + services.put(CHANNEL_AMBILIGHT_TOP_COLOR, ambilightService); + services.put(CHANNEL_AMBILIGHT_BOTTOM_COLOR, ambilightService); + + services.put(CHANNEL_TV_CHANNEL, new TvChannelService(this, connectionManager)); + services.put(CHANNEL_POWER, new PowerService(this, connectionManager)); + services.put(CHANNEL_SEARCH_CONTENT, new SearchContentService(this, connectionManager)); + channelServices = Collections.unmodifiableMap(services); + } + + /** + * Starts the pairing Process with the TV, which results in a Pairing Code shown on TV. + */ + private void initPairingCodeRetrieval(HttpHost target) + throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + logger.info("Pairing code for tv authentication is missing. " + + "Starting initial pairing process. Please provide manually the pairing code shown on the tv at the configuration of the tv thing."); + PhilipsTVPairing pairing = new PhilipsTVPairing(); + pairing.requestPairingPin(target); + } + + private boolean initCredentialsRetrieval(HttpHost target, String pincode) { + boolean hasFailed = false; + logger.info( + "Pairing code is available, but username and/or password is missing. Therefore we try to grant authorization and retrieve username and password."); + PhilipsTVPairing pairing = new PhilipsTVPairing(); + try { + if (pincode.isEmpty()) { + pairing.finishPairingWithTv(config.pairingCode, this, target); + } else { + pairing.finishPairingWithTv(pincode, this, target); + } + postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "offline.authentication-with-philips-tv-device-was-successful-continuing-initialization-of-the-tv"); + } catch (Exception e) { + postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "offline.could-not-successfully-finish-pairing-process-with-the-tv"); + logger.warn("Error during finishing pairing process with the TV: {}", e.getMessage(), e); + hasFailed = true; + } + return hasFailed; + } + + // callback methods for channel services + public void postUpdateChannel(String channelUID, State state) { + handler.updateChannelState(channelUID, state); + } + + public synchronized void postUpdateThing(ThingStatus status, ThingStatusDetail statusDetail, String msg) { + logger.trace("postUpdateThing {} {} {}", status, statusDetail, msg); + if (status == ThingStatus.ONLINE) { + if (msg.equalsIgnoreCase(STANDBY_MSG)) { + handler.updateChannelState(CHANNEL_POWER, OnOffType.OFF); + } else { + handler.updateChannelState(CHANNEL_POWER, OnOffType.ON); + startDeviceHealthJob(5, TimeUnit.SECONDS); + pendingPowerOn = false; + } + if (isSchedulerInitializable()) { // Init refresh scheduler only, if pairing is completed + startRefreshScheduler(); + } + } else if (status == ThingStatus.OFFLINE) { + handler.updateChannelState(CHANNEL_POWER, OnOffType.OFF); + if (!TV_NOT_LISTENING_MSG.equals(msg)) { // avoid cancelling refresh if TV is temporarily not available + ScheduledFuture refreshScheduler = this.refreshScheduler; + if (refreshScheduler != null) { + if (config.useUpnpDiscovery && isRefreshSchedulerRunning.test(refreshScheduler)) { + stopRefreshScheduler(); + } + } + // Reset app and channel list (if existing) for new retrieval during next startup + Map channelServices = this.channelServices; + @Nullable + PhilipsTVService appnameService = channelServices.get(CHANNEL_APPNAME); + if (appnameService != null) { + ((AppService) appnameService).clearAvailableAppList(); + } + @Nullable + PhilipsTVService tvchannelService = channelServices.get(CHANNEL_TV_CHANNEL); + if (tvchannelService != null) { + ((TvChannelService) tvchannelService).clearAvailableTvChannelList(); + } + } + } + updateStatus(status, statusDetail, msg); + } + + private boolean isSchedulerInitializable() { + String username = this.username; + String password = this.password; + boolean schedulerIsDone = false; + ScheduledFuture refreshScheduler = this.refreshScheduler; + if (refreshScheduler != null) { + schedulerIsDone = refreshScheduler.isDone(); + } + return (!username.isEmpty()) && (!password.isEmpty()) && ((refreshScheduler == null) || schedulerIsDone); + } + + private void startRefreshScheduler() { + int configuredRefreshRateOrDefault = Optional.ofNullable(config.refreshRate).orElse(10); + if (configuredRefreshRateOrDefault > 0) { // If value equals zero, refreshing should not be scheduled + ScheduledFuture refreshScheduler = this.refreshScheduler; + if (refreshScheduler != null) { + logger.debug("Refresh Scheduler already started for Philips TV {}, terminating.", handler.getThingID()); + if (isRefreshSchedulerRunning.test(refreshScheduler)) { + stopRefreshScheduler(); + } + } + logger.debug("Starting Refresh Scheduler for Philips TV {} with refresh rate of {}.", handler.getThingID(), + configuredRefreshRateOrDefault); + this.refreshScheduler = scheduler.scheduleWithFixedDelay(this::refreshTvProperties, 10, + configuredRefreshRateOrDefault, TimeUnit.SECONDS); + } + } + + private void stopRefreshScheduler() { + logger.debug("Stopping Refresh Scheduler for Philips TV: {}", handler.getThingID()); + ScheduledFuture refreshScheduler = this.refreshScheduler; + if (refreshScheduler != null) { + refreshScheduler.cancel(true); + } + } + + private void refreshTvProperties() { + try { + boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS); + if (isLockAcquired) { + try { + if (isOnline) { + Map channelServices = this.channelServices; + @Nullable + PhilipsTVService powerService = channelServices.get(CHANNEL_POWER); + if (powerService != null) { + powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH); + } + @Nullable + PhilipsTVService volumeService = channelServices.get(CHANNEL_VOLUME); + if (volumeService != null) { + volumeService.handleCommand(CHANNEL_VOLUME, RefreshType.REFRESH); + } + @Nullable + PhilipsTVService appnameService = channelServices.get(CHANNEL_APPNAME); + if (appnameService != null) { + appnameService.handleCommand(CHANNEL_APPNAME, RefreshType.REFRESH); + } + @Nullable + PhilipsTVService tvchannelService = channelServices.get(CHANNEL_TV_CHANNEL); + if (tvchannelService != null) { + tvchannelService.handleCommand(CHANNEL_TV_CHANNEL, RefreshType.REFRESH); + } + } + } finally { + lock.unlock(); + } + } + } catch (InterruptedException e) { + logger.warn("Exception occurred during refreshing the tv properties: {}", e.getMessage()); + } + } + + public void updateChannelStateDescription(final String channelId, Map values) { + AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider = this.stateDescriptionProvider; + List options = new ArrayList<>(); + if (!values.isEmpty()) { + values.forEach((key, value) -> options.add(new StateOption(key, value))); + stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThingUID(), channelId), options); + } + } + + @Override + public void thingDiscovered(DiscoveryService source, DiscoveryResult result) { + logger.debug("thingDiscovered: {}", result); + + if (config.useUpnpDiscovery && config.ipAddress.equals(result.getProperties().get(HOST))) { + upnpThingUID = result.getThingUID(); + logger.debug("thingDiscovered, thingUID={}, discoveredUID={}", handler.getThingUID(), upnpThingUID); + Map channelServices = this.channelServices; + @Nullable + PhilipsTVService powerService = channelServices.get(CHANNEL_POWER); + if (powerService != null) { + powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH); + } + } + } + + @Override + public void thingRemoved(DiscoveryService discoveryService, ThingUID thingUID) { + logger.debug("thingRemoved: {}", thingUID); + + if (thingUID.equals(upnpThingUID)) { + postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.standby"); + } + } + + @Override + public @Nullable Collection removeOlderResults(DiscoveryService discoveryService, long l, + @Nullable Collection collection, @Nullable ThingUID thingUID) { + return Collections.emptyList(); + } + + public void dispose() { + discoveryServiceRegistry.removeDiscoveryListener(this); + ScheduledFuture refreshScheduler = this.refreshScheduler; + if (refreshScheduler != null) { + if (isRefreshSchedulerRunning.test(refreshScheduler)) { + stopRefreshScheduler(); + } + } + ScheduledFuture deviceHealthJob = this.deviceHealthJob; + if (deviceHealthJob != null) { + deviceHealthJob.cancel(true); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java new file mode 100644 index 0000000000000..c157244e7b33e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.GET_NETWORK_DEVICES_PATH; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link WakeOnLanUtil} is offering methods for powering on TVs via Wake-On-LAN. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public final class WakeOnLanUtil { + + private static final int MAX_WOL_RETRIES = 10; + + private static final int WOL_PORT = 9; + + private static final Logger LOGGER = LoggerFactory.getLogger(WakeOnLanUtil.class); + + private static final Pattern MAC_PATTERN = Pattern.compile("([:\\-])"); + + private static final Predicate IS_WOL_ENABLED = j -> j.get("wake-on-lan").asText() + .equalsIgnoreCase("Enabled"); + + private static final Predicate IS_NOT_LOOPBACK = ni -> { + try { + return !ni.isLoopback(); + } catch (SocketException e) { + return false; + } + }; + + private WakeOnLanUtil() { + } + + public static Optional getMacFromEnabledInterface(ConnectionManager connectionManager) throws IOException { + String jsonContent = connectionManager.doHttpsGet(GET_NETWORK_DEVICES_PATH); + List jsonNode = OBJECT_MAPPER.readValue(jsonContent, new TypeReference>() { + }); + + return jsonNode.stream().filter(IS_WOL_ENABLED).map(j -> j.get("mac").asText()) + .peek(m -> LOGGER.debug("Mac identified as: {}", m)).findFirst(); + } + + public static void wakeOnLan(String ip, String mac) throws IOException, InterruptedException { + for (int i = 0; i < MAX_WOL_RETRIES; i++) { + if (isReachable(ip)) { + Thread.sleep(2000); + return; + } else { + Thread.sleep(100); + sendWakeOnLanPackage(mac); + } + } + } + + private static void sendWakeOnLanPackage(String mac) throws IOException { + byte[] macBytes = getMacBytes(mac); + byte[] bytes = new byte[6 + (16 * macBytes.length)]; + for (int i = 0; i < 6; i++) { + bytes[i] = (byte) 0xff; + } + for (int i = 6; i < bytes.length; i += macBytes.length) { + System.arraycopy(macBytes, 0, bytes, i, macBytes.length); + } + + List broadcastAddresses = Collections.list(NetworkInterface.getNetworkInterfaces()).stream() + .filter(IS_NOT_LOOPBACK).map(NetworkInterface::getInterfaceAddresses).flatMap(Collection::stream) + .map(InterfaceAddress::getBroadcast).filter(Objects::nonNull).collect(Collectors.toList()); + + for (InetAddress broadcast : broadcastAddresses) { + DatagramPacket packet = new DatagramPacket(bytes, bytes.length, broadcast, WOL_PORT); + try (DatagramSocket socket = new DatagramSocket()) { + LOGGER.debug("WOL sent to Broadcast-IP {} with MAC {}", broadcast, mac); + socket.send(packet); + } + } + } + + private static byte[] getMacBytes(String mac) { + byte[] bytes = new byte[6]; + String[] hex = MAC_PATTERN.split(mac); + if (hex.length != 6) { + throw new IllegalArgumentException("Invalid MAC address."); + } + try { + for (int i = 0; i < 6; i++) { + bytes[i] = (byte) Integer.parseInt(hex[i], 16); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid hex digit in MAC address."); + } + return bytes; + } + + public static boolean isReachable(String ipAddress) throws IOException { + InetAddress inetAddress = InetAddress.getByName(ipAddress); + return inetAddress.isReachable(1000); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java new file mode 100644 index 0000000000000..632dc50520583 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.discovery; + +import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.jupnp.model.meta.DeviceDetails; +import org.jupnp.model.meta.ModelDetails; +import org.jupnp.model.meta.RemoteDevice; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PhilipsTVDiscoveryParticipant} is responsible for discovering Philips TV devices through UPnP. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +@Component(immediate = true) +public class PhilipsTVDiscoveryParticipant implements UpnpDiscoveryParticipant { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public Set getSupportedThingTypeUIDs() { + return Collections.singleton(THING_TYPE_PHILIPSTV); + } + + @Override + public @Nullable DiscoveryResult createResult(RemoteDevice device) { + final ThingUID uid = getThingUID(device); + if (uid == null) { + return null; + } + + final Map properties = new HashMap<>(2); + String ipAddress = device.getIdentity().getDescriptorURL().getHost(); + properties.put(PARAMETER_IP_ADDRESS, ipAddress); + properties.put(PARAMETER_PHILIPSTV_PORT, DEFAULT_PORT); + logger.debug("Philips TV Found: {}, using default port {}", ipAddress, DEFAULT_PORT); + String friendlyName = device.getDetails().getFriendlyName(); + if (friendlyName.length() > 0 && Character.isDigit(friendlyName.charAt(0))) { + friendlyName = "_" + friendlyName; // label must not start with a digit + } + + return DiscoveryResultBuilder.create(uid).withThingType(THING_TYPE_PHILIPSTV).withProperties(properties) + .withLabel(friendlyName).build(); + } + + @Override + public @Nullable ThingUID getThingUID(RemoteDevice device) { + DeviceDetails details = device.getDetails(); + if (details != null) { + ModelDetails modelDetails = details.getModelDetails(); + if (modelDetails != null) { + String modelName = modelDetails.getModelName(); + String modelDescription = modelDetails.getModelDescription(); + if (modelName != null && modelDescription != null) { + if (modelName.contains("Philips TV")) { + logger.debug("Device found: {} with desc {}", modelName, modelDescription); + // One Philips TV contains several UPnP devices. + // Create unique Philips TV thing for every Media Renderer + // device and ignore rest of the UPnP devices. + if (modelDescription.contains("Media")) { + // UDN shouldn't contain '-' characters. + String udn = device.getIdentity().getUdn().getIdentifierString().replace("-", "_"); + logger.debug("Discovered a Philips TV '{}' model '{}' thing with UDN '{}'", + device.getDetails().getFriendlyName(), modelName, udn); + + return new ThingUID(THING_TYPE_PHILIPSTV, udn); + } + } + } + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java new file mode 100644 index 0000000000000..8b22a977f5c2e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java @@ -0,0 +1,204 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.pairing; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.BASE_PATH; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.EMPTY; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.SLASH; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Formatter; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.client.AuthCache; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.auth.DigestScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManagerUtil; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.AuthDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.DeviceDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.FinishPairingDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.PairingDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.RequestCodeDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PhilipsTVPairing} is responsible for the initial pairing process with the Philips TV. + * The outcome of this one-time pairing is a registered user with password, which will be used for + * controlling the tv. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class PhilipsTVPairing { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static String authTimestamp = ""; + + private static String authKey = ""; + + private static String deviceId = ""; + + private final String pairingBasePath = BASE_PATH + "pair" + SLASH; + + public void requestPairingPin(HttpHost target) + throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + RequestCodeDTO requestCodeDTO = new RequestCodeDTO( + Stream.of("read", "write", "control").collect(Collectors.toList()), createDeviceSpecification()); + + CloseableHttpClient httpClient = ConnectionManagerUtil.createSharedHttpClient(target, EMPTY, EMPTY); + ConnectionManager connectionManager = new ConnectionManager(httpClient, target); + String requestCodeJson = OBJECT_MAPPER.writeValueAsString(requestCodeDTO); + String requestPairingCodePath = pairingBasePath + "request"; + logger.debug("Request pairing code with json: {}", requestCodeJson); + PairingDTO pairingDTO = OBJECT_MAPPER + .readValue(connectionManager.doHttpsPost(requestPairingCodePath, requestCodeJson), PairingDTO.class); + + authTimestamp = pairingDTO.getTimestamp(); + authKey = pairingDTO.getAuthKey(); + + logger.info("The pairing code is valid for {} seconds.", pairingDTO.getTimeout()); + } + + public void finishPairingWithTv(String pairingCode, PhilipsTVConnectionManager handler, HttpHost target) + throws NoSuchAlgorithmException, InvalidKeyException, IOException, KeyStoreException, + KeyManagementException { + AuthDTO authDTO = new AuthDTO(); + authDTO.setAuthAppId("1"); + authDTO.setAuthSignature(calculateRFC2104HMAC(authTimestamp + pairingCode)); + authDTO.setAuthTimestamp(authTimestamp); + authDTO.setPin(pairingCode); + + FinishPairingDTO finishPairingDTO = new FinishPairingDTO(createDeviceSpecification(), authDTO); + String grantPairingJson = OBJECT_MAPPER.writeValueAsString(finishPairingDTO); + + Header challengeHeader = null; + try (CloseableHttpClient httpClient = ConnectionManagerUtil.createSharedHttpClient(target, EMPTY, EMPTY)) { + CloseableHttpResponse response = httpClient + .execute(new HttpGet(target.toURI() + pairingBasePath + "grant")); + challengeHeader = response.getFirstHeader("WWW-Authenticate"); + } catch (IOException e) { + logger.debug("finishPairingWithTv: {}", e.getMessage()); + throw e; + } + + try (CloseableHttpClient client = ConnectionManagerUtil.createSharedHttpClient(target, deviceId, authKey)) { + logger.debug("{} and device id: {} and auth_key: {}", grantPairingJson, deviceId, authKey); + + String grantPairingCodePath = pairingBasePath + "grant"; + HttpPost httpPost = new HttpPost(grantPairingCodePath); + httpPost.setHeader("Content-type", "application/json"); + httpPost.setEntity(new StringEntity(grantPairingJson)); + + DigestScheme digestAuth = new DigestScheme(); + digestAuth.processChallenge(challengeHeader); + + AuthCache authCache = new BasicAuthCache(); + authCache.put(target, digestAuth); + + HttpClientContext localContext = HttpClientContext.create(); + localContext.setAuthCache(authCache); + + try (CloseableHttpResponse response = client.execute(target, httpPost, localContext)) { + String jsonContent = EntityUtils.toString(response.getEntity()); + logger.debug("----------------------------------------"); + logger.debug("{}", response.getStatusLine()); + logger.debug("{}", jsonContent); + if (response.getStatusLine().getStatusCode() != 200) { + throw new IOException("Pairing grant failed"); + } + if (jsonContent.contains("INVALID_PIN")) { + throw new IOException("Invalid PIN"); + } + } + handler.setCreds(deviceId, authKey); + } catch (MalformedChallengeException e) { + logger.debug("finishPairingWithTv: {}", e.getMessage()); + throw new IOException(e.getMessage()); + } + } + + private String createDeviceId() { + StringBuilder deviceIdBuilder = new StringBuilder(); + String chars = "abcdefghkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789"; + for (int i = 0; i < 16; i++) { + int index = (int) Math.floor(Math.random() * chars.length()); + deviceIdBuilder.append(chars.charAt(index)); + } + return deviceIdBuilder.toString(); + } + + private DeviceDTO createDeviceSpecification() { + DeviceDTO deviceDTO = new DeviceDTO(); + deviceDTO.setAppName("openHAB"); + deviceDTO.setAppId("app.id"); + deviceDTO.setDeviceName("heliotrope"); + deviceDTO.setDeviceOs("Android"); + deviceDTO.setType("native"); + if (deviceId.isEmpty()) { + deviceId = createDeviceId(); + } + deviceDTO.setId(deviceId); + return deviceDTO; + } + + private String toHexString(byte[] bytes) { + try (Formatter formatter = new Formatter()) { + for (byte b : bytes) { + formatter.format("%02x", b); + } + + return formatter.toString(); + } + } + + private String calculateRFC2104HMAC(String data) + throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { + String hmacSHA1 = "HmacSHA1"; + // Key used for generated the HMAC signature + String secretKey = "ZmVay1EQVFOaZhwQ4Kv81ypLAZNczV9sG4KkseXWn1NEk6cXmPKO/MCa9sryslvLCFMnNe4Z4CPXzToowvhHvA=="; + Key signingKey = new SecretKeySpec(Base64.getDecoder().decode(secretKey), hmacSHA1); + Mac mac = Mac.getInstance(hmacSHA1); + mac.init(signingKey); + return Base64.getEncoder().encodeToString(toHexString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8.name()))) + .getBytes(StandardCharsets.UTF_8.name())); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java new file mode 100644 index 0000000000000..47f7e4ae1ad77 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link FinishPairingDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class AuthDTO { + + @JsonProperty("auth_signature") + private String authSignature = ""; + + @JsonProperty("auth_timestamp") + private String authTimestamp = ""; + + @JsonProperty("pin") + private String pin = ""; + + @JsonProperty("auth_AppId") + private String authAppId = ""; + + public void setAuthSignature(String authSignature) { + this.authSignature = authSignature; + } + + public String getAuthSignature() { + return authSignature; + } + + public void setAuthTimestamp(String authTimestamp) { + this.authTimestamp = authTimestamp; + } + + public String getAuthTimestamp() { + return authTimestamp; + } + + public void setPin(String pin) { + this.pin = pin; + } + + public String getPin() { + return pin; + } + + public void setAuthAppId(String authAppId) { + this.authAppId = authAppId; + } + + public String getAuthAppId() { + return authAppId; + } + + @Override + public String toString() { + return "Auth{" + "auth_signature = '" + authSignature + '\'' + ",auth_timestamp = '" + authTimestamp + '\'' + + ",pin = '" + pin + '\'' + ",auth_AppId = '" + authAppId + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java new file mode 100644 index 0000000000000..a63ffcc6cf2b7 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link RequestCodeDTO} and {@link FinishPairingDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class DeviceDTO { + + @JsonProperty("app_name") + private String appName = ""; + + @JsonProperty("device_name") + private String deviceName = ""; + + @JsonProperty("id") + private String id = ""; + + @JsonProperty("type") + private String type = ""; + + @JsonProperty("app_id") + private String appId = ""; + + @JsonProperty("device_os") + private String deviceOs = ""; + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getAppName() { + return appName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getDeviceName() { + return deviceName; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAppId() { + return appId; + } + + public void setDeviceOs(String deviceOs) { + this.deviceOs = deviceOs; + } + + public String getDeviceOs() { + return deviceOs; + } + + @Override + public String toString() { + return "Device{" + "app_name = '" + appName + '\'' + ",device_name = '" + deviceName + '\'' + ",id = '" + id + + '\'' + ",type = '" + type + '\'' + ",app_id = '" + appId + '\'' + ",device_os = '" + deviceOs + '\'' + + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java new file mode 100644 index 0000000000000..c28ea0989d291 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link FinishPairingDTO} class defines the Data Transfer Object + * for the Philips TV API /pair/grant endpoint to finish pairing. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class FinishPairingDTO { + + @JsonProperty("auth") + private AuthDTO auth; + + @JsonProperty("device") + private DeviceDTO device; + + public FinishPairingDTO(DeviceDTO device, AuthDTO auth) { + this.device = device; + this.auth = auth; + } + + public void setAuth(AuthDTO auth) { + this.auth = auth; + } + + public AuthDTO getAuth() { + return auth; + } + + public void setDevice(DeviceDTO device) { + this.device = device; + } + + public DeviceDTO getDevice() { + return device; + } + + @Override + public String toString() { + return "FinishPairingDTO{" + "auth = '" + auth + '\'' + ",device = '" + device + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java new file mode 100644 index 0000000000000..bfe1175d83728 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Response Data Transfer Object of {@link RequestCodeDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class PairingDTO { + + @JsonProperty("auth_key") + private String authKey = ""; + + @JsonProperty("timestamp") + private String timestamp = ""; + + @JsonProperty("timeout") + private String timeout = ""; + + public String getTimeout() { + return timeout; + } + + public void setTimeout(String timeout) { + this.timeout = timeout; + } + + public void setAuthKey(String authKey) { + this.authKey = authKey; + } + + public String getAuthKey() { + return authKey; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getTimestamp() { + return timestamp; + } + + @Override + public String toString() { + return "PairingCodeDTO{" + "auth_key = '" + authKey + '\'' + ",timestamp = '" + timestamp + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java new file mode 100644 index 0000000000000..e72a0b47d0712 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link RequestCodeDTO} class defines the Data Transfer Object + * for the Philips TV API /pair/request endpoint to request a pairing code. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class RequestCodeDTO { + + @JsonProperty("scope") + private List scope; + + @JsonProperty("device") + private DeviceDTO device; + + public RequestCodeDTO(List scope, DeviceDTO device) { + this.scope = scope; + this.device = device; + } + + public void setScope(List scope) { + this.scope = scope; + } + + public List getScope() { + return scope; + } + + public void setDevice(DeviceDTO device) { + this.device = device; + } + + public DeviceDTO getDevice() { + return device; + } + + @Override + public String toString() { + return "RequestPinDTO{" + "scope = '" + scope + '\'' + ",device = '" + device + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java new file mode 100644 index 0000000000000..3cf2f5c97e015 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java @@ -0,0 +1,316 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.WakeOnLanUtil; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.DataDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsUpdateDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValueDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValuesDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorDeltaDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorSettingsDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightConfigDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightLoungeDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightModeDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightPowerDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightTopologyDTO; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Service for handling commands regarding Ambilight settings of the TV + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class AmbilightService implements PhilipsTVService { + + private static final List AMBILIGHT_COLOR_CHANNELS = Stream + .of(CHANNEL_AMBILIGHT_COLOR, CHANNEL_AMBILIGHT_LEFT_COLOR, CHANNEL_AMBILIGHT_RIGHT_COLOR, + CHANNEL_AMBILIGHT_TOP_COLOR, CHANNEL_AMBILIGHT_BOTTOM_COLOR) + .collect(Collectors.toList()); + private static final int AMBILIGHT_HUE_NODE_ID = 2131230774; + private static final int AMBILIGHT_BRIGHTNESS_NODE_ID = 2131230769; + private static final String AMBILIGHT_MODE_MANUAL = "manual"; + private static final String AMBILIGHT_STYLE_FOLLOW_VIDEO = "FOLLOW_VIDEO"; + private static final String AMBILIGHT_STYLE_FOLLOW_COLOR = "FOLLOW_COLOR"; + private static final String AMBILIGHT_ALGORITHM_MANUAL_HUE = "MANUAL HUE"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PhilipsTVConnectionManager handler; + + private final boolean isWakeOnLanEnabled; + + private @Nullable AmbilightTopologyDTO ambilightTopology; + + private final ConnectionManager connectionManager; + + public AmbilightService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + this.isWakeOnLanEnabled = handler.getMacAddress().isEmpty() ? false : true; + } + + @Override + public void handleCommand(String channel, Command command) { + try { + if (CHANNEL_AMBILIGHT_POWER.equals(channel) && (command instanceof OnOffType)) { + setAmbilightPowerState(command); + } else if (CHANNEL_AMBILIGHT_POWER.equals(channel) && (command instanceof RefreshType)) { + AmbilightPowerDTO ambilightPowerDTO = getAmbilightPowerState(); + handler.postUpdateChannel(CHANNEL_AMBILIGHT_POWER, + ambilightPowerDTO.isPoweredOn() ? OnOffType.ON : OnOffType.OFF); + } else if (CHANNEL_AMBILIGHT_HUE_POWER.equals(channel) && (command instanceof OnOffType)) { + setAmbilightHuePowerState(command); + } else if (CHANNEL_AMBILIGHT_LOUNGE_POWER.equals(channel) && (command instanceof OnOffType)) { + setAmbilightLoungePowerState(command); + } else if (CHANNEL_AMBILIGHT_STYLE.equals(channel) && (command instanceof StringType)) { + setAmbilightStyle(command.toString()); + } else if (CHANNEL_AMBILIGHT_STYLE.equals(channel) && (command instanceof RefreshType)) { + AmbilightConfigDTO config = getAmbilightConfig(); + String styleWithAlgorithm = String.format("%s %s", config.getStyleName(), config.getMenuSetting()); + handler.postUpdateChannel(CHANNEL_AMBILIGHT_STYLE, new StringType(styleWithAlgorithm)); + } else if (CHANNEL_AMBILIGHT_COLOR.equals(channel) && (command instanceof HSBType)) { + setAllAmbilightColors((HSBType) command); + } else if ((CHANNEL_AMBILIGHT_LEFT_COLOR.equals(channel) || CHANNEL_AMBILIGHT_RIGHT_COLOR.equals(channel) + || CHANNEL_AMBILIGHT_TOP_COLOR.equals(channel) || CHANNEL_AMBILIGHT_BOTTOM_COLOR.equals(channel)) + && (command instanceof HSBType)) { + setAmbilightPixel((HSBType) command, channel); + } else if (AMBILIGHT_COLOR_CHANNELS.contains(channel) && (command instanceof PercentType)) { + setAmbilightBrightness(((PercentType) command).intValue()); + } else { + if (!(command instanceof RefreshType)) { + logger.warn("Unknown command: {} for Channel {}", command, channel); + } + } + } catch (Exception e) { + if (isTvOfflineException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Error during handling the Ambilight command {} for Channel {}: {}", command, channel, + e.getMessage(), e); + } + } + } + + private AmbilightPowerDTO getAmbilightPowerState() throws IOException { + return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_POWERSTATE_PATH), + AmbilightPowerDTO.class); + } + + private void setAmbilightPowerState(Command command) throws IOException { + if (command.equals(OnOffType.OFF)) { + AmbilightPowerDTO ambilightPower = new AmbilightPowerDTO(); + ambilightPower.setPower(POWER_OFF); + String powerStateJson = OBJECT_MAPPER.writeValueAsString(ambilightPower); + logger.debug("Post Ambilight power state json: {}", powerStateJson); + connectionManager.doHttpsPost(AMBILIGHT_POWERSTATE_PATH, powerStateJson); + } else { // power on via setting FOLLOW_VIDEO instead through POWERSTATE_PATH which sets FOLLOW_COLOR + setAmbilightStyle(String.format("%s %s", AMBILIGHT_STYLE_FOLLOW_VIDEO, "STANDARD")); + } + } + + private void setAmbilightHuePowerState(Command command) throws IOException { + DataDTO data = new DataDTO((command.equals(OnOffType.ON) ? "true" : "false")); + + ValueDTO value = new ValueDTO(data); + value.setNodeid(AMBILIGHT_HUE_NODE_ID); + value.setAvailable("true"); + value.setControllable("true"); + + ValuesDTO values = new ValuesDTO(value); + TvSettingsUpdateDTO ambilightHuePower = new TvSettingsUpdateDTO(Collections.singletonList(values)); + + String ambilightHuePowerJson = OBJECT_MAPPER.writeValueAsString(ambilightHuePower); + logger.debug("Post Ambilight hue power state json: {}", ambilightHuePowerJson); + connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, ambilightHuePowerJson); + } + + private void setAmbilightLoungePowerState(Command command) throws IOException, InterruptedException { + AmbilightColorDTO ambilightColorDTO = new AmbilightColorDTO(); + if (command.equals(OnOffType.ON)) { + if (isWakeOnLanEnabled && !WakeOnLanUtil.isReachable(handler.config.ipAddress)) { + WakeOnLanUtil.wakeOnLan(handler.config.ipAddress, handler.getMacAddress()); + } + ambilightColorDTO.setHue(0); + } else { + ambilightColorDTO.setHue(255); + } + AmbilightLoungeDTO ambilightLoungeDTO = new AmbilightLoungeDTO(ambilightColorDTO); + + String setAmbilightLoungeJson = OBJECT_MAPPER.writeValueAsString(ambilightLoungeDTO); + logger.debug("Setting ambilight lounge power state json: {}", setAmbilightLoungeJson); + connectionManager.doHttpsPost(AMBILIGHT_LOUNGE_PATH, setAmbilightLoungeJson); + } + + private void setAmbilightStyle(String styleToSet) throws IOException { + String[] styleWithAlgorithm = styleToSet.split(" "); + if (styleWithAlgorithm.length != 2) { + throw new IllegalStateException("Style and/or algorithm is missing."); + } + String style = styleWithAlgorithm[0]; + String algorithm = styleWithAlgorithm[1]; + AmbilightConfigDTO ambilightConfig = new AmbilightConfigDTO( + new AmbilightColorSettingsDTO(new AmbilightColorDTO(), new AmbilightColorDeltaDTO())); + ambilightConfig.setStyleName(style); + ambilightConfig.setMenuSetting(algorithm); + if (style.equals(AMBILIGHT_STYLE_FOLLOW_COLOR) && algorithm.equals(AMBILIGHT_ALGORITHM_MANUAL_HUE)) { + ambilightConfig.setAlgorithm(algorithm); + ambilightConfig.setIsExpert(true); + AmbilightColorDeltaDTO ambilightColorDeltaDTO = new AmbilightColorDeltaDTO(); + ambilightColorDeltaDTO.setHue(0); + ambilightColorDeltaDTO.setBrightness(0); + ambilightColorDeltaDTO.setSaturation(0); + AmbilightColorSettingsDTO ambilightColorSettingsDTO = new AmbilightColorSettingsDTO(new AmbilightColorDTO(), + ambilightColorDeltaDTO); + ambilightColorSettingsDTO.setSpeed(255); + ambilightConfig.setColorSettings(ambilightColorSettingsDTO); + } + String ambilightConfigJson = OBJECT_MAPPER.writeValueAsString(ambilightConfig); + logger.debug("Post config for Ambilight style json: {}", ambilightConfigJson); + connectionManager.doHttpsPost(AMBILIGHT_CONFIG_PATH, ambilightConfigJson); + } + + private AmbilightConfigDTO getAmbilightConfig() throws IOException { + return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_CONFIG_PATH), AmbilightConfigDTO.class); + } + + private void setAmbilightMode(String mode) throws IOException { + AmbilightModeDTO ambilightMode = new AmbilightModeDTO(); + ambilightMode.setCurrent(mode); + String ambilightModeJson = OBJECT_MAPPER.writeValueAsString(ambilightMode); + logger.debug("Post ambilight mode json: {}", ambilightModeJson); + connectionManager.doHttpsPost(AMBILIGHT_MODE_PATH, ambilightModeJson); + } + + // private AmbilightModeDTO getAmbilightMode() throws IOException { + // return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_MODE_PATH), AmbilightModeDTO.class); + // } + + private void setAmbilightBrightness(int brightnessToSet) throws IOException { + String ambilightBrightnessJson = ServiceUtil.createTvSettingsUpdateJson(AMBILIGHT_BRIGHTNESS_NODE_ID, + brightnessToSet / 10); + logger.debug("Post Ambilight brightness json: {}", ambilightBrightnessJson); + connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, ambilightBrightnessJson); + } + + private void setAmbilightPixel(HSBType hsb, String channel) throws IOException { + if (ambilightTopology == null) { + ambilightTopology = getAmbilightTopology(); + } + setAmbilightMode(AMBILIGHT_MODE_MANUAL); // activates the usage of cached values + String sideToSet = determineAmbilightSide(channel); + int pixelSize = ambilightTopology.getPixelSizeForGivenSide(sideToSet); + + ObjectNode rootNode = OBJECT_MAPPER.createObjectNode(); + + ObjectNode pixel = OBJECT_MAPPER.createObjectNode(); + pixel.put("r", hsb.getRed().intValue()); + pixel.put("g", hsb.getGreen().intValue()); + pixel.put("b", hsb.getBlue().intValue()); + + ObjectNode sidePixels = OBJECT_MAPPER.createObjectNode(); + // pixel declaration in json start with 0 + IntStream.range(0, pixelSize).forEach(i -> sidePixels.set(Integer.toString(i), pixel)); + + IntStream.rangeClosed(1, ambilightTopology.getLayers()).forEach(i -> { + ObjectNode layerX = OBJECT_MAPPER.createObjectNode(); + layerX.set(sideToSet, sidePixels); + + rootNode.set("layer" + i, layerX); + }); + + String ambilightPixelJson = OBJECT_MAPPER.writeValueAsString(rootNode); + logger.debug("Sending {} Ambilight pixel json: {}", sideToSet, ambilightPixelJson); + connectionManager.doHttpsPost(AMBILIGHT_CACHED_PATH, ambilightPixelJson); + } + + private AmbilightTopologyDTO getAmbilightTopology() throws IOException { + return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_TOPOLOGY_PATH), + AmbilightTopologyDTO.class); + } + + private String determineAmbilightSide(String channel) { + String sideToSet; + switch (channel) { + case CHANNEL_AMBILIGHT_LEFT_COLOR: + sideToSet = "left"; + break; + case CHANNEL_AMBILIGHT_RIGHT_COLOR: + sideToSet = "right"; + break; + case CHANNEL_AMBILIGHT_TOP_COLOR: + sideToSet = "top"; + break; + case CHANNEL_AMBILIGHT_BOTTOM_COLOR: + sideToSet = "bottom"; + break; + default: + throw new IllegalStateException("Unexpected channel for ambilight pixel set: " + channel); + } + return sideToSet; + } + + private void setAllAmbilightColors(HSBType hsb) throws IOException { + AmbilightColorDTO ambilightColor = new AmbilightColorDTO(hsb); + AmbilightColorDeltaDTO ambilightColorDelta = new AmbilightColorDeltaDTO(); + ambilightColorDelta.setHue(0); + ambilightColorDelta.setSaturation(0); + ambilightColorDelta.setBrightness(0); + + AmbilightColorSettingsDTO ambilightColorSettings = new AmbilightColorSettingsDTO(ambilightColor, + ambilightColorDelta); + ambilightColorSettings.setSpeed(255); + + AmbilightConfigDTO ambilightConfig = new AmbilightConfigDTO(ambilightColorSettings); + ambilightConfig.setIsExpert(true); + ambilightConfig.setStyleName("FOLLOW_COLOR"); + ambilightConfig.setAlgorithm("MANUAL_HUE"); + + String setAmbilightColorsJson = OBJECT_MAPPER.writeValueAsString(ambilightConfig); + logger.debug("Setting ambilight colors json: {}", setAmbilightColorsJson); + connectionManager.doHttpsPost(AMBILIGHT_CONFIG_PATH, setAmbilightColorsJson); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java new file mode 100644 index 0000000000000..8e893e29d65df --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.http.ParseException; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ApplicationsDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.AvailableAppsDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ComponentDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.CurrentAppDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ExtrasDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.IntentDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.LaunchAppDTO; +import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AppService} is responsible for handling key code commands, which emulate a button + * press on a remote control. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class AppService implements PhilipsTVService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Label , Entry of App + private @Nullable Map> availableApps; + + private String currentPackageName = ""; + + private final PhilipsTVConnectionManager handler; + + private final ConnectionManager connectionManager; + + public AppService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + } + + @Override + public void handleCommand(String channel, Command command) { + try { + synchronized (this) { + if (isAvailableAppListEmpty()) { + getAvailableAppListFromTv(); + handler.updateChannelStateDescription(CHANNEL_APPNAME, availableApps.keySet().stream() + .collect(Collectors.toMap(Function.identity(), Function.identity()))); + } + } + if (command instanceof RefreshType) { + // Get current App name + String packageName = getCurrentApp(); + if (currentPackageName.equals(packageName)) { + return; + } else { + currentPackageName = packageName; + } + Optional>> app = availableApps.entrySet() + .stream().filter(e -> e.getValue().getKey().equalsIgnoreCase(packageName)).findFirst(); + if (app.isPresent()) { + handler.postUpdateChannel(CHANNEL_APP, new StringType(packageName)); + Map.Entry> appEntry = app.get(); + handler.postUpdateChannel(CHANNEL_APPNAME, new StringType(appEntry.getKey())); + // Get icon for current App + RawType image = getIconForApp(appEntry.getValue().getKey(), appEntry.getValue().getValue()); + handler.postUpdateChannel(CHANNEL_APP_ICON, (image != null) ? image : UnDefType.UNDEF); + } else { // NA + handler.postUpdateChannel(CHANNEL_APP, new StringType(packageName)); + handler.postUpdateChannel(CHANNEL_APPNAME, new StringType(packageName)); + handler.postUpdateChannel(CHANNEL_APP_ICON, UnDefType.UNDEF); + } + } else if (command instanceof StringType) { + String appName = ""; + if (CHANNEL_APPNAME.equals(channel) && availableApps.containsKey(command.toString())) { + launchApp(command.toString()); + } else if (CHANNEL_APP.equals(channel)) { + launchDNApp(command.toString()); + } else { + logger.warn("The given App with Name: {} {} couldn't be found in the local App List from the tv.", + command, appName); + } + } else { + logger.warn("Unknown command: {} for Channel {}", command, channel); + } + } catch (Exception e) { + if (isTvOfflineException(e)) { + logger.debug("Could not execute command for apps, the TV is offline."); + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Error occurred during handling of command for apps: {}", e.getMessage(), e); + } + } + } + + private boolean isAvailableAppListEmpty() { + return (availableApps == null) || availableApps.isEmpty(); + } + + private void launchDNApp(String appName) throws IOException { + for (Map.Entry> entry : availableApps.entrySet()) { + Map.Entry app = entry.getValue(); + if (app.getKey().equals(appName)) { + logger.debug("Found app by dn: {} {} {}", entry.getKey(), app.getKey(), app.getValue()); + launchApp(entry.getKey()); + return; + } + } + logger.warn("The given App with DN: {} couldn't be found in the local App List from the tv.", appName); + } + + private void launchApp(String appName) throws IOException { + Map.Entry app = availableApps.get(appName); + + ComponentDTO componentDTO = new ComponentDTO(); + componentDTO.setPackageName(app.getKey()); + componentDTO.setClassName(app.getValue()); + + IntentDTO intentDTO = new IntentDTO(componentDTO, new ExtrasDTO()); + intentDTO.setAction("empty"); + LaunchAppDTO launchAppDTO = new LaunchAppDTO(intentDTO); + String appLaunchJson = OBJECT_MAPPER.writeValueAsString(launchAppDTO); + + logger.debug("App Launch json: {}", appLaunchJson); + connectionManager.doHttpsPost(LAUNCH_APP_PATH, appLaunchJson); + } + + private String getCurrentApp() throws IOException, ParseException { + CurrentAppDTO currentAppDTO = OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(GET_CURRENT_APP_PATH), + CurrentAppDTO.class); + return currentAppDTO.getComponent().getPackageName(); + } + + private @Nullable RawType getIconForApp(String packageName, String className) throws IOException { + String pathForIcon = String.format("%s%s-%s%sicon", SLASH, className, packageName, SLASH); + byte[] icon = connectionManager + .doHttpsGetForImage(String.format("%s%s", GET_AVAILABLE_APP_LIST_PATH, pathForIcon)); + if ((icon != null) && (icon.length > 0)) { + return new RawType(icon, "image/png"); + } else { + return null; + } + } + + private void getAvailableAppListFromTv() throws IOException { + AvailableAppsDTO availableAppsDTO = OBJECT_MAPPER + .readValue(connectionManager.doHttpsGet(GET_AVAILABLE_APP_LIST_PATH), AvailableAppsDTO.class); + + ConcurrentMap> appsMap = availableAppsDTO.getApplications() + .stream() + .collect(Collectors.toConcurrentMap(ApplicationsDTO::getLabel, + a -> new AbstractMap.SimpleEntry<>(a.getIntent().getComponent().getPackageName(), + a.getIntent().getComponent().getClassName()), + (a1, a2) -> a1)); + + logger.debug("appsMap - Apps added: {}", appsMap.size()); + if (logger.isTraceEnabled()) { + appsMap.keySet().forEach(app -> logger.trace("appsMap - App found: {}", app)); + } + + this.availableApps = appsMap; + } + + public void clearAvailableAppList() { + if (availableApps != null) { + availableApps.clear(); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java new file mode 100644 index 0000000000000..96fd973cfce78 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import java.util.Arrays; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * The {@link KeyPress} presents all available key codes of Philips TV. + * + * @see http://jointspace.sourceforge.net/projectdata/documentation/jasonApi/1/doc/API-Method-input-key-POST.html + * + * + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public enum KeyPress { + + KEY_STANDBY("Standby"), + KEY_BACK("Back"), + KEY_FIND("Find"), + KEY_RED_COLOR("RedColour"), + KEY_GREEN_COLOR("GreenColour"), + KEY_YELLOW_COLOR("YellowColour"), + KEY_BLUE_COLOR("BlueColour"), + KEY_HOME("Home"), + KEY_VOLUME_UP("VolumeUp"), + KEY_VOLUME_DOWN("VolumeDown"), + KEY_MUTE("Mute"), + KEY_OPTIONS("Options"), + KEY_DOT("Dot"), + KEY_0("Digit0"), + KEY_1("Digit1"), + KEY_2("Digit2"), + KEY_3("Digit3"), + KEY_4("Digit4"), + KEY_5("Digit5"), + KEY_6("Digit6"), + KEY_7("Digit7"), + KEY_8("Digit8"), + KEY_9("Digit9"), + KEY_INFO("Info"), + KEY_CURSOR_UP("CursorUp"), + KEY_CURSOR_DOWN("CursorDown"), + KEY_CURSOR_LEFT("CursorLeft"), + KEY_CURSOR_RIGHT("CursorRight"), + KEY_CONFIRM("Confirm"), + KEY_NEXT("Next"), + KEY_PREVIOUS("Previous"), + KEY_ADJUST("Adjust"), + KEY_WATCH_TV("WatchTV"), + KEY_VIEW_MODE("Viewmode"), + KEY_TELETEXT("Teletext"), + KEY_SUBTITLE("Subtitle"), + KEY_CHANNEL_STEP_UP("ChannelStepUp"), + KEY_CHANNEL_STEP_DOWN("ChannelStepDown"), + KEY_SOURCE("Source"), + KEY_AMBILIGHT_ON_OFF("AmbilightOnOff"), + KEY_PLAY("Play"), + KEY_PAUSE("Pause"), + KEY_FAST_FORWARD("FastForward"), + KEY_STOP("Stop"), + KEY_REWIND("Rewind"), + KEY_RECORD("Record"), + KEY_ONLINE("Online"); + + private final String value; + + KeyPress(String value) { + this.value = value; + } + + public static KeyPress getKeyPressForValue(String value) throws IllegalArgumentException { + return Arrays.stream(values()).filter(v -> v.value.equalsIgnoreCase(value)).findFirst() + .orElseThrow(() -> new IllegalArgumentException("Key code could not be recognized: " + value)); + } + + @JsonValue + @Override + public String toString() { + return this.value; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java new file mode 100644 index 0000000000000..fb61c3844d3c7 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.keypress.KeyPressDTO; +import org.openhab.core.library.types.NextPreviousType; +import org.openhab.core.library.types.PlayPauseType; +import org.openhab.core.library.types.RewindFastforwardType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link KeyPressService} is responsible for handling key code commands, which emulate a button + * press on a remote control. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class KeyPressService implements PhilipsTVService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PhilipsTVConnectionManager handler; + + private final ConnectionManager connectionManager; + + public KeyPressService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + } + + @Override + public void handleCommand(String channel, Command command) { + KeyPress keyPress = null; + if (isSupportedCommand(command)) { + // Three approaches to resolve the KEY_CODE + try { + keyPress = KeyPress.valueOf(command.toString().toUpperCase()); + } catch (IllegalArgumentException e) { + try { + keyPress = KeyPress.valueOf("KEY_" + command.toString().toUpperCase()); + } catch (IllegalArgumentException e2) { + try { + keyPress = KeyPress.getKeyPressForValue(command.toString()); + } catch (IllegalArgumentException e3) { + logger.trace("KeyPress threw IllegalArgumentException", e3); + } + } + } + + if (keyPress != null) { + try { + sendKeyPress(keyPress); + } catch (Exception e) { + if (isTvOfflineException(e)) { + logger.debug("Could not execute command for key code, the TV is offline."); + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Unknown error occurred while sending keyPress code {}: {}", keyPress, + e.getMessage(), e); + } + } + } else { + logger.warn("Command '{}' not a supported keyPress code.", command); + } + } + } + + private static boolean isSupportedCommand(Command command) { + return (command instanceof StringType) || (command instanceof NextPreviousType) + || (command instanceof PlayPauseType) || (command instanceof RewindFastforwardType); + } + + private void sendKeyPress(KeyPress key) throws IOException { + KeyPressDTO keyPressDTO = new KeyPressDTO(key); + String keyPressJson = OBJECT_MAPPER.writeValueAsString(keyPressDTO); + logger.debug("KeyPress Json sent: {}", keyPressJson); + connectionManager.doHttpsPost(KEY_CODE_PATH, keyPressJson); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java new file mode 100644 index 0000000000000..fcf9e211f441c --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.IOException; + +import org.apache.http.ParseException; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.WakeOnLanUtil; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.power.PowerStateDTO; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PowerService} is responsible for handling power states commands, which are sent to the + * power channel. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class PowerService implements PhilipsTVService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PhilipsTVConnectionManager handler; + + private final ConnectionManager connectionManager; + + private final boolean isWakeOnLanEnabled; + + public PowerService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + this.isWakeOnLanEnabled = handler.getMacAddress().isEmpty() ? false : true; + } + + @Override + public void handleCommand(String channel, Command command) { + try { + if (command instanceof RefreshType) { + PowerStateDTO powerStateDTO = getPowerState(); + if (powerStateDTO.isPoweredOn()) { + handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.online"); + } else if (powerStateDTO.isStandby()) { + handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.standby"); + if (powerStateDTO.isStandbyKeep()) { + handler.checkPendingPowerOn(); + } + } else { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, EMPTY); + } + } else if (command instanceof OnOffType) { + setPowerState((OnOffType) command); + if (command == OnOffType.ON) { + handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.online"); + } else { + handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.standby"); + } + } else { + logger.warn("Unknown command: {} for Channel {}", command, channel); + } + } catch (Exception e) { + if (isTvOfflineException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Unexpected Error handling the PowerState command {} for Channel {}: {}", command, channel, + e.getMessage()); + } + } + } + + private PowerStateDTO getPowerState() throws IOException, ParseException { + return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(TV_POWERSTATE_PATH), PowerStateDTO.class); + } + + private void setPowerState(OnOffType onOffType) throws IOException, InterruptedException { + PowerStateDTO powerStateDTO = new PowerStateDTO(); + if (onOffType == OnOffType.ON) { + if (isWakeOnLanEnabled && !WakeOnLanUtil.isReachable(handler.config.ipAddress)) { + WakeOnLanUtil.wakeOnLan(handler.config.ipAddress, handler.getMacAddress()); + } + powerStateDTO.setPowerState(POWER_ON); + } else { + powerStateDTO.setPowerState(STANDBY); + } + + String powerStateJson = OBJECT_MAPPER.writeValueAsString(powerStateDTO); + logger.debug("PowerState Json sent: {}", powerStateJson); + connectionManager.doHttpsPost(TV_POWERSTATE_PATH, powerStateJson); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java new file mode 100644 index 0000000000000..344d1222d56d0 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ComponentDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ExtrasDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.IntentDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.LaunchAppDTO; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for toggling the Google Assistant on the Philips TV + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class SearchContentService implements PhilipsTVService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PhilipsTVConnectionManager handler; + + private final ConnectionManager connectionManager; + + public SearchContentService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + } + + @Override + public void handleCommand(String channel, Command command) { + if (command instanceof StringType) { + try { + searchForContentOnTv(command.toString()); + } catch (Exception e) { + if (isTvOfflineException(e)) { + logger.warn("Could not search content on Philips TV: TV is offline."); + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Error during the launch of search content on Philips TV: {}", e.getMessage(), e); + } + } + } else if (!(command instanceof RefreshType)) { + logger.warn("Unknown command: {} for Channel {}", command, channel); + } + } + + private void searchForContentOnTv(String searchContent) throws IOException { + ExtrasDTO extrasDTO = new ExtrasDTO(); + extrasDTO.setQuery(searchContent); + + IntentDTO intentDTO = new IntentDTO(new ComponentDTO(), extrasDTO); + intentDTO.setAction("android.search.action.GLOBAL_SEARCH"); + LaunchAppDTO launchAppDTO = new LaunchAppDTO(intentDTO); + + String searchContentLaunch = OBJECT_MAPPER.writeValueAsString(launchAppDTO); + + logger.debug("Search Content Launch json: {}", searchContentLaunch); + connectionManager.doHttpsPost(LAUNCH_APP_PATH, searchContentLaunch); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java new file mode 100644 index 0000000000000..a2b62f4a45f53 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; + +import java.util.Collections; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.DataDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.NodesDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsCurrentDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsUpdateDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValueDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValuesDTO; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Util class for common used methods from philips tv services + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +final class ServiceUtil { + + private ServiceUtil() { + } + + static String createTvSettingsRetrievalJson(int nodeId) throws JsonProcessingException { + NodesDTO nodes = new NodesDTO(); + nodes.setNodeid(nodeId); + TvSettingsCurrentDTO tvSettingCurrent = new TvSettingsCurrentDTO(Collections.singletonList(nodes)); + return OBJECT_MAPPER.writeValueAsString(tvSettingCurrent); + } + + static String createTvSettingsUpdateJson(int nodeId, int valueToSet) throws JsonProcessingException { + DataDTO data = new DataDTO(valueToSet); + ValueDTO value = new ValueDTO(data); + value.setNodeid(nodeId); + ValuesDTO values = new ValuesDTO(value); + values.setValue(value); + TvSettingsUpdateDTO tvSetting = new TvSettingsUpdateDTO(Collections.singletonList(values)); + return OBJECT_MAPPER.writeValueAsString(tvSetting); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java new file mode 100644 index 0000000000000..53ae7fb3c39aa --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.AvailableTvChannelsDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.ChannelDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.ChannelListDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.TvChannelDTO; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for handling commands regarding setting or retrieving the TV channel + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class TvChannelService implements PhilipsTVService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Name , ccid of TV Channel + private @Nullable Map availableTvChannels; + + private final PhilipsTVConnectionManager handler; + + private final ConnectionManager connectionManager; + + public TvChannelService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + } + + @Override + public void handleCommand(String channel, Command command) { + try { + synchronized (this) { + if (isTvChannelListEmpty()) { + availableTvChannels = getAvailableTvChannelListFromTv(); + handler.updateChannelStateDescription(CHANNEL_TV_CHANNEL, availableTvChannels.keySet().stream() + .collect(Collectors.toMap(Function.identity(), Function.identity()))); + } + } + if (command instanceof RefreshType) { + // Get current tv channel name + String tvChannelName = getCurrentTvChannel(); + handler.postUpdateChannel(CHANNEL_TV_CHANNEL, new StringType(tvChannelName)); + } else if (command instanceof StringType) { + if (availableTvChannels.containsKey(command.toString())) { + switchTvChannel(command); + } else { + logger.warn( + "The given TV Channel with Name: {} couldn't be found in the local Channel List from the TV.", + command); + } + } else { + logger.warn("Unknown command: {} for Channel {}", command, channel); + } + } catch (Exception e) { + if (isTvOfflineException(e)) { + logger.warn("Could not execute command for TV Channels, the TV is offline."); + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Error occurred during handling of command for TV Channels: {}", e.getMessage(), e); + } + } + } + + private boolean isTvChannelListEmpty() { + return (availableTvChannels == null) || availableTvChannels.isEmpty(); + } + + private Map getAvailableTvChannelListFromTv() throws IOException { + AvailableTvChannelsDTO availableTvChannelsDTO = OBJECT_MAPPER.readValue( + connectionManager.doHttpsGet(GET_AVAILABLE_TV_CHANNEL_LIST_PATH), AvailableTvChannelsDTO.class); + + ConcurrentMap tvChannelsMap = availableTvChannelsDTO.getChannel().stream() + .collect(Collectors.toConcurrentMap(ChannelDTO::getName, ChannelDTO::getCcid, (c1, c2) -> c1)); + + logger.debug("TV Channels added: {}", tvChannelsMap.size()); + if (logger.isTraceEnabled()) { + tvChannelsMap.keySet().forEach(app -> logger.trace("TV Channel found: {}", app)); + } + return tvChannelsMap; + } + + private String getCurrentTvChannel() throws IOException { + TvChannelDTO tvChannelDTO = OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(TV_CHANNEL_PATH), + TvChannelDTO.class); + return Optional.ofNullable(tvChannelDTO.getChannel()).map(ChannelDTO::getName).orElse("NA"); + } + + private void switchTvChannel(Command command) throws IOException { + ChannelDTO channelDTO = new ChannelDTO(); + channelDTO.setCcid(availableTvChannels.get(command.toString())); + + ChannelListDTO channelListDTO = new ChannelListDTO(); + channelListDTO.setId("allter"); + channelListDTO.setVersion("30"); + + TvChannelDTO tvChannelDTO = new TvChannelDTO(channelDTO, channelListDTO); + String switchTvChannelJson = OBJECT_MAPPER.writeValueAsString(tvChannelDTO); + logger.debug("Switch TV Channel json: {}", switchTvChannelJson); + connectionManager.doHttpsPost(TV_CHANNEL_PATH, switchTvChannelJson); + } + + public void clearAvailableTvChannelList() { + if (availableTvChannels != null) { + availableTvChannels.clear(); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java new file mode 100644 index 0000000000000..214d843a2d426 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsUpdateDTO; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for handling commands regarding the TV picture settings + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class TvPictureService implements PhilipsTVService { + + private static final int SHARPNESS_NODE_ID = 2131230851; + private static final int CONTRAST_NODE_ID = 2131230850; + private static final int BRIGHTNESS_NODE_ID = 2131230852; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PhilipsTVConnectionManager handler; + + private final ConnectionManager connectionManager; + + public TvPictureService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + } + + @Override + public void handleCommand(String channel, Command command) { + try { + if (CHANNEL_BRIGHTNESS.equals(channel) && command instanceof PercentType) { + setBrightness(((PercentType) command).intValue()); + } else if (CHANNEL_BRIGHTNESS.equals(channel) && command instanceof RefreshType) { + int currentBrightness = getBrightness(); + handler.postUpdateChannel(CHANNEL_BRIGHTNESS, new PercentType(currentBrightness)); + } else if (CHANNEL_CONTRAST.equals(channel) && command instanceof PercentType) { + setContrast(((PercentType) command).intValue()); + } else if (CHANNEL_CONTRAST.equals(channel) && command instanceof RefreshType) { + int currentContrast = getContrast(); + handler.postUpdateChannel(CHANNEL_CONTRAST, new PercentType(currentContrast)); + } else if (CHANNEL_SHARPNESS.equals(channel) && command instanceof PercentType) { + setSharpness(((PercentType) command).intValue()); + } else if (CHANNEL_SHARPNESS.equals(channel) && command instanceof RefreshType) { + int currentSharpness = getSharpness(); + handler.postUpdateChannel(CHANNEL_SHARPNESS, new PercentType(currentSharpness)); + } else { + if (!(command instanceof RefreshType)) { + logger.warn("Unknown command: {} for Channel {}", command, channel); + } + } + } catch (Exception e) { + if (isTvOfflineException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Error during handling the TvPicture command {} for Channel {}: {}", command, channel, + e.getMessage(), e); + } + } + } + + private int getBrightness() throws IOException { + String getBrightnessJson = ServiceUtil.createTvSettingsRetrievalJson(BRIGHTNESS_NODE_ID); + logger.debug("Post Tv Picture retrieval brightness json: {}", getBrightnessJson); + return (int) OBJECT_MAPPER.readValue(connectionManager.doHttpsPost(CURRENT_SETTINGS_PATH, getBrightnessJson), + TvSettingsUpdateDTO.class).getValues().get(0).getValue().getData().getValue(); + } + + private void setBrightness(int brightness) throws IOException { + String tvPictureBrightnessJson = ServiceUtil.createTvSettingsUpdateJson(BRIGHTNESS_NODE_ID, brightness); + logger.debug("Post Tv Picture brightness json: {}", tvPictureBrightnessJson); + connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, tvPictureBrightnessJson); + } + + private int getContrast() throws IOException { + String getContrastJson = ServiceUtil.createTvSettingsRetrievalJson(CONTRAST_NODE_ID); + logger.debug("Post Tv Picture retrieval contrast json: {}", getContrastJson); + return (int) OBJECT_MAPPER.readValue(connectionManager.doHttpsPost(CURRENT_SETTINGS_PATH, getContrastJson), + TvSettingsUpdateDTO.class).getValues().get(0).getValue().getData().getValue(); + } + + private void setContrast(int contrast) throws IOException { + String tvPictureContrastJson = ServiceUtil.createTvSettingsUpdateJson(CONTRAST_NODE_ID, contrast); + logger.debug("Post Tv Picture contrast json: {}", tvPictureContrastJson); + connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, tvPictureContrastJson); + } + + private int getSharpness() throws IOException { + String getSharpnessJson = ServiceUtil.createTvSettingsRetrievalJson(SHARPNESS_NODE_ID); + logger.debug("Post Tv Picture retrieval sharpness json: {}", getSharpnessJson); + return (int) OBJECT_MAPPER.readValue(connectionManager.doHttpsPost(CURRENT_SETTINGS_PATH, getSharpnessJson), + TvSettingsUpdateDTO.class).getValues().get(0).getValue().getData().getValue(); + } + + private void setSharpness(int sharpness) throws IOException { + String tvPictureSharpnessJson = ServiceUtil.createTvSettingsUpdateJson(SHARPNESS_NODE_ID, sharpness / 10); + logger.debug("Post Tv Picture brightness json: {}", tvPictureSharpnessJson); + connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, tvPictureSharpnessJson); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java new file mode 100644 index 0000000000000..7fa2859415ff7 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service; + +import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.service.KeyPress.KEY_MUTE; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.keypress.KeyPressDTO; +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.volume.VolumeDTO; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link VolumeService} is responsible for handling volume commands, which are sent to the + * volume channel or mute channel. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public class VolumeService implements PhilipsTVService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final PhilipsTVConnectionManager handler; + + private final ConnectionManager connectionManager; + + public VolumeService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) { + this.handler = handler; + this.connectionManager = connectionManager; + } + + @Override + public void handleCommand(String channel, Command command) { + try { + if (command instanceof RefreshType) { + VolumeDTO volumeDTO = getVolume(); + handler.postUpdateChannel(CHANNEL_VOLUME, new PercentType(volumeDTO.getCurrentVolume())); + handler.postUpdateChannel(CHANNEL_MUTE, volumeDTO.isMuted() ? OnOffType.ON : OnOffType.OFF); + } else if (CHANNEL_VOLUME.equals(channel) && command instanceof PercentType) { + setVolume((PercentType) command); + handler.postUpdateChannel(CHANNEL_VOLUME, (PercentType) command); + } else if (CHANNEL_MUTE.equals(channel) && command instanceof OnOffType) { + setMute(); + } else { + logger.warn("Unknown command: {} for Channel {}", command, channel); + } + } catch (Exception e) { + if (isTvOfflineException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG); + } else if (isTvNotListeningException(e)) { + handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + TV_NOT_LISTENING_MSG); + } else { + logger.warn("Error during handling the VolumeService command {} for Channel {}: {}", command, channel, + e.getMessage(), e); + } + } + } + + private VolumeDTO getVolume() throws IOException { + String jsonContent = connectionManager.doHttpsGet(VOLUME_PATH); + return OBJECT_MAPPER.readValue(jsonContent, VolumeDTO.class); + } + + private void setVolume(PercentType volumeToSet) throws IOException { + VolumeDTO volumeDTO = new VolumeDTO(); + volumeDTO.setMuted(false); + volumeDTO.setCurrentVolume(volumeToSet.intValue()); + String volumeJson = OBJECT_MAPPER.writeValueAsString(volumeDTO); + logger.debug("Set json volume: {}", volumeJson); + connectionManager.doHttpsPost(VOLUME_PATH, volumeJson); + } + + private void setMute() throws IOException { + // We just sent the KEY_MUTE and dont bother what was actually requested + KeyPressDTO keyPressDTO = new KeyPressDTO(KEY_MUTE); + String muteJson = OBJECT_MAPPER.writeValueAsString(keyPressDTO); + logger.debug("Set json mute state: {}", muteJson); + connectionManager.doHttpsPost(KEY_CODE_PATH, muteJson); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java new file mode 100644 index 0000000000000..20b900b32d67d --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.api; + +import java.net.NoRouteToHostException; +import java.util.Optional; + +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.HttpHostConnectException; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.types.Command; + +/** + * Interface for Philips TV services. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +@NonNullByDefault +public interface PhilipsTVService { + + /** + * Procedure for sending command. + * + * @param channel the channel to which the command applies + * @param command the command to be handled + */ + void handleCommand(String channel, Command command); + + default boolean isTvOfflineException(Exception exception) { + String message = Optional.ofNullable(exception.getMessage()).orElse(""); + if (!message.isEmpty()) { + if ((exception instanceof NoRouteToHostException) && message.contains("Host unreachable")) { + return true; + } else { + return (exception instanceof ConnectTimeoutException) && message.contains("timed out"); + } + } else { + return false; + } + } + + default boolean isTvNotListeningException(Exception exception) { + String message = Optional.ofNullable(exception.getMessage()).orElse(""); + if (!message.isEmpty()) { + return (exception instanceof HttpHostConnectException) && message.contains("Connection refused"); + } else { + return false; + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java new file mode 100644 index 0000000000000..b065f44d94c07 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link TvSettingsUpdateDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class DataDTO { + + @JsonProperty + private Object value; // can be int or string + + public DataDTO() { + } + + public DataDTO(Object value) { + this.value = value; + } + + public void setValue(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return "Data{" + "value = '" + value + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java new file mode 100644 index 0000000000000..feb909ae51e4d --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link TvSettingsCurrentDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class NodesDTO { + + @JsonProperty("nodeid") + private int nodeid; + + public void setNodeid(int nodeid) { + this.nodeid = nodeid; + } + + public int getNodeid() { + return nodeid; + } + + @Override + public String toString() { + return "NodesItem{" + "nodeid = '" + nodeid + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java new file mode 100644 index 0000000000000..321252394f2eb --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link TvSettingsCurrentDTO} class defines the Data Transfer Object + * for the POST Request to Philips TV API /menuitems/settings/current endpoint to retrieve current settings of the tv, + * e.g. the tv picture brightness. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class TvSettingsCurrentDTO { + + @JsonProperty("nodes") + private List nodes; + + public TvSettingsCurrentDTO() { + } + + public TvSettingsCurrentDTO(List nodes) { + this.nodes = nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public List getNodes() { + return nodes; + } + + @Override + public String toString() { + return "TvSettingsCurrentDTO{" + "nodes = '" + nodes + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java new file mode 100644 index 0000000000000..b6210cdf2a29b --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link TvSettingsUpdateDTO} class defines the Data Transfer Object + * for the Philips TV API /menuitems/settings/update endpoint to update settings of the tv, e.g. turning on/off + * ambilight hue power. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class TvSettingsUpdateDTO { + + @JsonProperty + private List values; + + public TvSettingsUpdateDTO() { + } + + public TvSettingsUpdateDTO(List values) { + this.values = values; + } + + public void setValues(List values) { + this.values = values; + } + + public List getValues() { + return values; + } + + @Override + public String toString() { + return "TvSettingsDTO{" + "values = '" + values + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java new file mode 100644 index 0000000000000..e3113fc879e3e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link TvSettingsUpdateDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class ValueDTO { + + @JsonProperty("Controllable") + private String controllable = ""; + + @JsonProperty + private DataDTO data; + + @JsonProperty("Nodeid") + private int nodeid; + + @JsonProperty("Available") + private String available = ""; + + public ValueDTO() { + } + + public ValueDTO(DataDTO data) { + this.data = data; + } + + public void setControllable(String controllable) { + this.controllable = controllable; + } + + public String getControllable() { + return controllable; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public DataDTO getData() { + return data; + } + + public void setNodeid(int nodeid) { + this.nodeid = nodeid; + } + + public int getNodeid() { + return nodeid; + } + + public void setAvailable(String available) { + this.available = available; + } + + public String getAvailable() { + return available; + } + + @Override + public String toString() { + return "Value{" + "controllable = '" + controllable + '\'' + ",data = '" + data + '\'' + ",nodeid = '" + nodeid + + '\'' + ",available = '" + available + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java new file mode 100644 index 0000000000000..e4a2e8d72db08 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link TvSettingsUpdateDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class ValuesDTO { + + @JsonProperty + private ValueDTO value; + + public ValuesDTO() { + } + + public ValuesDTO(ValueDTO value) { + this.value = value; + } + + public void setValue(ValueDTO value) { + this.value = value; + } + + public ValueDTO getValue() { + return value; + } + + @Override + public String toString() { + return "ValuesItem{" + "value = '" + value + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java new file mode 100644 index 0000000000000..f1d9b885441b8 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import org.openhab.core.library.types.HSBType; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link AmbilightColorSettingsDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class AmbilightColorDTO { + + @JsonProperty("saturation") + private int saturation; + + @JsonProperty("brightness") + private int brightness; + + @JsonProperty("hue") + private int hue; + + public AmbilightColorDTO() { + } + + public AmbilightColorDTO(HSBType hsb) { + hue = hsb.getHue().intValue() * 255 / 360; + saturation = hsb.getSaturation().intValue() * 255 / 100; + brightness = hsb.getBrightness().intValue() * 255 / 100; + } + + public void setSaturation(int saturation) { + this.saturation = saturation; + } + + public int getSaturation() { + return saturation; + } + + public void setBrightness(int brightness) { + this.brightness = brightness; + } + + public int getBrightness() { + return brightness; + } + + public void setHue(int hue) { + this.hue = hue; + } + + public int getHue() { + return hue; + } + + @Override + public String toString() { + return "Color{" + "saturation = '" + saturation + '\'' + ",brightness = '" + brightness + '\'' + ",hue = '" + + hue + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java new file mode 100644 index 0000000000000..963150114302a --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link AmbilightColorSettingsDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class AmbilightColorDeltaDTO { + + @JsonProperty("saturation") + private int saturation; + + @JsonProperty("brightness") + private int brightness; + + @JsonProperty("hue") + private int hue; + + public void setSaturation(int saturation) { + this.saturation = saturation; + } + + public int getSaturation() { + return saturation; + } + + public void setBrightness(int brightness) { + this.brightness = brightness; + } + + public int getBrightness() { + return brightness; + } + + public void setHue(int hue) { + this.hue = hue; + } + + public int getHue() { + return hue; + } + + @Override + public String toString() { + return "ColorDelta{" + "saturation = '" + saturation + '\'' + ",brightness = '" + brightness + '\'' + ",hue = '" + + hue + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java new file mode 100644 index 0000000000000..fdae3bc3eee64 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link AmbilightConfigDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class AmbilightColorSettingsDTO { + + @JsonProperty("color") + private AmbilightColorDTO color; + + @JsonProperty("colorDelta") + private AmbilightColorDeltaDTO colorDelta; + + @JsonProperty("speed") + private int speed; + + public AmbilightColorSettingsDTO(AmbilightColorDTO color, AmbilightColorDeltaDTO colorDelta) { + this.color = color; + this.colorDelta = colorDelta; + } + + public void setColor(AmbilightColorDTO color) { + this.color = color; + } + + public AmbilightColorDTO getColor() { + return color; + } + + public void setColorDelta(AmbilightColorDeltaDTO colorDelta) { + this.colorDelta = colorDelta; + } + + public AmbilightColorDeltaDTO getColorDelta() { + return colorDelta; + } + + public void setSpeed(int speed) { + this.speed = speed; + } + + public int getSpeed() { + return speed; + } + + @Override + public String toString() { + return "ColorSettings{" + "color = '" + color + '\'' + ",colorDelta = '" + colorDelta + '\'' + ",speed = '" + + speed + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java new file mode 100644 index 0000000000000..584a3358e244c --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link AmbilightConfigDTO} class defines the Data Transfer Object + * for the Philips TV API /ambilight/currentconfiguration endpoint to retrieve or set the current ambilight style. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class AmbilightConfigDTO { + + @JsonProperty("isExpert") + private boolean isExpert; + + @JsonProperty("menuSetting") + private String menuSetting = ""; + + @JsonProperty("styleName") + private String styleName = ""; + + @JsonProperty("colorSettings") + private AmbilightColorSettingsDTO colorSettings; + + @JsonProperty("algorithm") + private String algorithm = ""; + + public AmbilightConfigDTO() { + } + + public AmbilightConfigDTO(AmbilightColorSettingsDTO colorSettings) { + this.colorSettings = colorSettings; + } + + public void setMenuSetting(String menuSetting) { + this.menuSetting = menuSetting; + } + + public String getMenuSetting() { + return menuSetting; + } + + public void setStyleName(String styleName) { + this.styleName = styleName; + } + + public String getStyleName() { + return styleName; + } + + public boolean isIsExpert() { + return isExpert; + } + + public void setIsExpert(boolean isExpert) { + this.isExpert = isExpert; + } + + public AmbilightColorSettingsDTO getColorSettings() { + return colorSettings; + } + + public void setColorSettings(AmbilightColorSettingsDTO colorSettings) { + this.colorSettings = colorSettings; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + @Override + public String toString() { + return "AmbilightConfigDTO{" + "isExpert = '" + isExpert + '\'' + ",menuSetting = '" + menuSetting + '\'' + + ",styleName = '" + styleName + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java new file mode 100644 index 0000000000000..8f0fbf36429da --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link AmbilightLoungeDTO} class defines the Data Transfer Object + * for the Philips TV API /ambilight/lounge endpoint to power on or off the ambilight lounge mode. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class AmbilightLoungeDTO { + + @JsonProperty("color") + private AmbilightColorDTO color; + + public AmbilightLoungeDTO(AmbilightColorDTO color) { + this.color = color; + } + + public void setColor(AmbilightColorDTO color) { + this.color = color; + } + + public AmbilightColorDTO getColor() { + return color; + } + + @Override + public String toString() { + return "AmbilightLoungeDTO{" + "color = '" + color + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java new file mode 100644 index 0000000000000..bc4ef0d5f1e91 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link AmbilightModeDTO} class defines the Data Transfer Object + * for the Philips TV API /ambilight/mode endpoint to retrieve or set the ambilight mode. + *

+ * current (string): One of following values: + *

+ * internal: The internal ambilight algorithm is used to calculate the ambilight colours. + *

+ * manual: The cached ambilight colours are shown. + *

+ * expert: The cached ambilight colours are used as input for the internal ambilight algorithm + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class AmbilightModeDTO { + + @JsonProperty("current") + private String current = ""; + + public AmbilightModeDTO() { + } + + public void setCurrent(String current) { + this.current = current; + } + + public String getCurrent() { + return current; + } + + @Override + public String toString() { + return "AmbilightModeDTO{" + "current = '" + current + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java new file mode 100644 index 0000000000000..21793b7082e9a --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.POWER_ON; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link AmbilightPowerDTO} class defines the Data Transfer Object + * for the Philips TV API /ambilight/power endpoint to retrieve or set the current power state. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class AmbilightPowerDTO { + + @JsonProperty("power") + private String power = ""; + + public String getPower() { + return power; + } + + public void setPower(String power) { + this.power = power; + } + + @JsonIgnore + public boolean isPoweredOn() { + return power.equalsIgnoreCase(POWER_ON); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java new file mode 100644 index 0000000000000..4393d9fa1a0fe --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link AmbilightTopologyDTO} class defines the Data Transfer Object + * for the Philips TV API /ambilight/topology endpoint to retrieve the ambilight topology information. + *

+ * Endpoint returns: + *

+ * layers (integer): The number of layers. + *

+ * left (integer): The number of pixels on the left. + *

+ * top (integer): The number of pixels on the top. + *

+ * right (integer): The number of pixels on the right. + *

+ * bottom (integer): The number of pixels on the bottom. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class AmbilightTopologyDTO { + + @JsonProperty("top") + private int top; + + @JsonProperty("left") + private int left; + + @JsonProperty("bottom") + private int bottom; + + @JsonProperty("layers") + private int layers; + + @JsonProperty("right") + private int right; + + public AmbilightTopologyDTO() { + } + + public void setTop(int top) { + this.top = top; + } + + public int getTop() { + return top; + } + + public void setLeft(int left) { + this.left = left; + } + + public int getLeft() { + return left; + } + + public void setBottom(int bottom) { + this.bottom = bottom; + } + + public int getBottom() { + return bottom; + } + + public void setLayers(int layers) { + this.layers = layers; + } + + public int getLayers() { + return layers; + } + + public void setRight(int right) { + this.right = right; + } + + public int getRight() { + return right; + } + + @JsonIgnore + public int getPixelSizeForGivenSide(String side) { + int value; + switch (side) { + case "left": + value = left; + break; + case "right": + value = right; + break; + case "top": + value = top; + break; + case "bottom": + value = bottom; + break; + default: + throw new IllegalStateException("Unexpected side: " + side); + } + return value; + } + + @Override + public String toString() { + return "AmbilightTopologyDTO{" + "top = '" + top + '\'' + ",left = '" + left + '\'' + ",bottom = '" + bottom + + '\'' + ",layers = '" + layers + '\'' + ",right = '" + right + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java new file mode 100644 index 0000000000000..b30b6408d981b --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link AvailableAppsDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class ApplicationsDTO { + + @JsonProperty("label") + private String label = ""; + + @JsonProperty("id") + private String id = ""; + + @JsonProperty("type") + private String type = ""; + + @JsonProperty("intent") + private IntentDTO intent; + + @JsonProperty("order") + private int order; + + public ApplicationsDTO() { + } + + public ApplicationsDTO(IntentDTO intent) { + this.intent = intent; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setIntent(IntentDTO intent) { + this.intent = intent; + } + + public IntentDTO getIntent() { + return intent; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return order; + } + + @Override + public String toString() { + return "ApplicationsItem{" + "label = '" + label + '\'' + ",id = '" + id + '\'' + ",type = '" + type + '\'' + + ",intent = '" + intent + '\'' + ",order = '" + order + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java new file mode 100644 index 0000000000000..6097790c6204f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application; + +import java.util.List; + +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link AvailableAppsDTO} class defines the Data Transfer Object + * for the Philips TV API /applications endpoint for retrieving all installed apps. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class AvailableAppsDTO { + + @JsonProperty("version") + private int version; + + @JsonProperty("applications") + private @Nullable List applications; + + public AvailableAppsDTO() { + } + + public void setVersion(int version) { + this.version = version; + } + + public int getVersion() { + return version; + } + + public void setApplications(List applications) { + this.applications = applications; + } + + public @Nullable List getApplications() { + return applications; + } + + @Override + public String toString() { + return "AvailableAppsDTO{" + "version = '" + version + '\'' + ",applications = '" + applications + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java new file mode 100644 index 0000000000000..bddad4304df28 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link LaunchAppDTO} and {@link CurrentAppDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class ComponentDTO { + + @JsonProperty("className") + private String className = ""; + + @JsonProperty("packageName") + private String packageName = ""; + + public void setClassName(String className) { + this.className = className; + } + + public String getClassName() { + return className; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getPackageName() { + return packageName; + } + + @Override + public String toString() { + return "Component{" + "className = '" + className + '\'' + ",packageName = '" + packageName + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java new file mode 100644 index 0000000000000..e5dae87ae8781 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link LaunchAppDTO} class defines the Data Transfer Object + * for the Philips TV API /activities/current endpoint for retrieving the current running TV app. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class CurrentAppDTO { + + @JsonProperty("component") + private ComponentDTO component; + + public CurrentAppDTO() { + } + + public CurrentAppDTO(ComponentDTO component) { + this.component = component; + } + + public void setComponent(ComponentDTO component) { + this.component = component; + } + + public ComponentDTO getComponent() { + return component; + } + + @Override + public String toString() { + return "Intent{" + "component = '" + component + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java new file mode 100644 index 0000000000000..5019c33193d9e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link IntentDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class ExtrasDTO { + + @JsonProperty("query") + private String query = ""; + + public void setQuery(String query) { + this.query = query; + } + + public String getQuery() { + return query; + } + + @Override + public String toString() { + return "Extras{" + "query = '" + query + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java new file mode 100644 index 0000000000000..53ac1fed71fec --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link LaunchAppDTO} and {@link LaunchAppDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class IntentDTO { + + @JsonProperty("component") + private ComponentDTO component; + + @JsonProperty("action") + private String action = ""; + + @JsonProperty("extras") + private ExtrasDTO extras; + + public IntentDTO() { + } + + public IntentDTO(ComponentDTO component, ExtrasDTO extras) { + this.component = component; + this.extras = extras; + } + + public void setComponent(ComponentDTO component) { + this.component = component; + } + + public ComponentDTO getComponent() { + return component; + } + + public void setAction(String action) { + this.action = action; + } + + public String getAction() { + return action; + } + + public void setExtras(ExtrasDTO extras) { + this.extras = extras; + } + + public ExtrasDTO getExtras() { + return extras; + } + + @Override + public String toString() { + return "Intent{" + "component = '" + component + '\'' + ",action = '" + action + '\'' + ",extras = '" + extras + + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java new file mode 100644 index 0000000000000..67fda9c421fda --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link LaunchAppDTO} class defines the Data Transfer Object + * for the Philips TV API /activities/launch endpoint for launching TV apps and launching search for content. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class LaunchAppDTO { + + @JsonProperty("intent") + private IntentDTO intent; + + public LaunchAppDTO() { + } + + public LaunchAppDTO(IntentDTO intent) { + this.intent = intent; + } + + public void setIntent(IntentDTO intent) { + this.intent = intent; + } + + public IntentDTO getIntent() { + return intent; + } + + @Override + public String toString() { + return "LaunchAppDTO{" + "intent = '" + intent + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java new file mode 100644 index 0000000000000..fc10fd820cf76 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel; + +import java.util.List; + +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link AvailableTvChannelsDTO} class defines the Data Transfer Object + * for the Philips TV API channeldb/tv/channelLists/all endpoint for retrieving all tv channels. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class AvailableTvChannelsDTO { + + @JsonProperty("Channel") + private @Nullable List channel; + + @JsonProperty("id") + private String id = ""; + + @JsonProperty("medium") + private String medium = ""; + + @JsonProperty("version") + private int version; + + @JsonProperty("listType") + private String listType = ""; + + @JsonProperty("operator") + private String operator = ""; + + @JsonProperty("installCountry") + private String installCountry = ""; + + public AvailableTvChannelsDTO() { + } + + public void setChannel(List channel) { + this.channel = channel; + } + + public @Nullable List getChannel() { + return channel; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setMedium(String medium) { + this.medium = medium; + } + + public String getMedium() { + return medium; + } + + public void setVersion(int version) { + this.version = version; + } + + public int getVersion() { + return version; + } + + public void setListType(String listType) { + this.listType = listType; + } + + public String getListType() { + return listType; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getOperator() { + return operator; + } + + public void setInstallCountry(String installCountry) { + this.installCountry = installCountry; + } + + public String getInstallCountry() { + return installCountry; + } + + @Override + public String toString() { + return "AvailableTvChannelsDTO{" + "channel = '" + channel + '\'' + ",id = '" + id + '\'' + ",medium = '" + + medium + '\'' + ",version = '" + version + '\'' + ",listType = '" + listType + '\'' + ",operator = '" + + operator + '\'' + ",installCountry = '" + installCountry + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java new file mode 100644 index 0000000000000..0ef476fdab57f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel; + +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link TvChannelDTO} and {@link AvailableTvChannelsDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class ChannelDTO { + + @JsonProperty("serviceType") + private String serviceType = ""; + + @JsonProperty("logoVersion") + private int logoVersion; + + @JsonProperty("ccid") + private String ccid = ""; + + @JsonProperty("name") + private String name = ""; + + @JsonProperty("preset") + private String preset = ""; + + @JsonProperty("tsid") + private int tsid; + + @JsonProperty("type") + private String type = ""; + + @JsonProperty("onid") + private int onid; + + @JsonProperty("sid") + private int sid; + + public void setServiceType(String serviceType) { + this.serviceType = serviceType; + } + + public String getServiceType() { + return serviceType; + } + + public void setLogoVersion(int logoVersion) { + this.logoVersion = logoVersion; + } + + public int getLogoVersion() { + return logoVersion; + } + + public void setCcid(@Nullable String ccid) { + if (!ccid.isEmpty()) { + this.ccid = ccid; + } + } + + public String getCcid() { + return ccid; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPreset(String preset) { + this.preset = preset; + } + + public String getPreset() { + return preset; + } + + public void setTsid(int tsid) { + this.tsid = tsid; + } + + public int getTsid() { + return tsid; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setOnid(int onid) { + this.onid = onid; + } + + public int getOnid() { + return onid; + } + + public void setSid(int sid) { + this.sid = sid; + } + + public int getSid() { + return sid; + } + + @Override + public String toString() { + return "ChannelItem{" + "serviceType = '" + serviceType + '\'' + ",logoVersion = '" + logoVersion + '\'' + + ",ccid = '" + ccid + '\'' + ",name = '" + name + '\'' + ",preset = '" + preset + '\'' + ",tsid = '" + + tsid + '\'' + ",type = '" + type + '\'' + ",onid = '" + onid + '\'' + ",sid = '" + sid + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java new file mode 100644 index 0000000000000..db6f13e060f39 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Part of {@link TvChannelDTO} + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class ChannelListDTO { + + @JsonProperty("id") + private String id = ""; + + @JsonProperty("version") + private String version = ""; + + public String getId() { + return id; + } + + public String getVersion() { + return version; + } + + public void setId(String id) { + this.id = id; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java new file mode 100644 index 0000000000000..b025452e6a5cd --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link TvChannelDTO} class defines the Data Transfer Object + * for the Philips TV API /activities/tv endpoint to get and switch tv channels. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class TvChannelDTO { + + @JsonProperty("channel") + private ChannelDTO channel; + + @JsonProperty("channelList") + private ChannelListDTO channelList; + + public TvChannelDTO() { + } + + public TvChannelDTO(ChannelDTO channel, ChannelListDTO channelList) { + this.channel = channel; + this.channelList = channelList; + } + + public ChannelDTO getChannel() { + return channel; + } + + public ChannelListDTO getChannelList() { + return channelList; + } + + public void setChannel(ChannelDTO channelDTO) { + this.channel = channelDTO; + } + + public void setChannelList(ChannelListDTO channelList) { + this.channelList = channelList; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java new file mode 100644 index 0000000000000..655fb265f74fa --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.keypress; + +import org.openhab.binding.androidtv.internal.protocol.philipstv.service.KeyPress; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link KeyPressDTO} class defines the Data Transfer Object + * for the Philips TV API /input/key endpoint for remote controller emulation. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ + +public class KeyPressDTO { + + @JsonProperty("key") + private KeyPress key; + + public KeyPressDTO() { + } + + public KeyPressDTO(KeyPress key) { + this.key = key; + } + + public KeyPress getKey() { + return key; + } + + public void setKey(KeyPress key) { + this.key = key; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java new file mode 100644 index 0000000000000..621379fa3b4c9 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.power; + +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.POWER_ON; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.STANDBY; +import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.STANDBYKEEP; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link PowerStateDTO} class defines the Data Transfer Object + * for the Philips TV API /powerstate endpoint to retrieve or set the current power state. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class PowerStateDTO { + + @JsonProperty("powerstate") + private String powerState = ""; + + public PowerStateDTO() { + } + + public String getPowerState() { + return powerState; + } + + public void setPowerState(String powerState) { + this.powerState = powerState; + } + + @JsonIgnore + public boolean isPoweredOn() { + return powerState.equalsIgnoreCase(POWER_ON); + } + + @JsonIgnore + public boolean isStandby() { + return (powerState.equalsIgnoreCase(STANDBY) || powerState.equalsIgnoreCase(STANDBYKEEP)); + } + + @JsonIgnore + public boolean isStandbyKeep() { + return powerState.equalsIgnoreCase(STANDBYKEEP); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java new file mode 100644 index 0000000000000..74dad6504e128 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.volume; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link VolumeDTO} class defines the Data Transfer Object + * for the Philips TV API /audio/volume endpoint. + * + * @author Benjamin Meyer - Initial contribution + * @author Ben Rosenblum - Merged into AndroidTV + */ +public class VolumeDTO { + + @JsonProperty("current") + private int currentVolume; + + @JsonProperty("muted") + private boolean muted; + + public VolumeDTO() { + } + + public int getCurrentVolume() { + return currentVolume; + } + + public void setCurrentVolume(int currentVolume) { + this.currentVolume = currentVolume; + } + + public boolean isMuted() { + return muted; + } + + public void setMuted(boolean muted) { + this.muted = muted; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties index 3e644d9af3fd5..516cb24bbe439 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties +++ b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties @@ -7,15 +7,17 @@ addon.androidtv.description = This is the add-on for AndroidTV. thing-type.androidtv.googletv.label = GoogleTV thing-type.androidtv.googletv.description = GoogleTV +thing-type.androidtv.philipstv.label = Philips TV +thing-type.androidtv.philipstv.description = A Philips TV device thing-type.androidtv.shieldtv.label = ShieldTV thing-type.androidtv.shieldtv.description = Nvidia ShieldTV # thing types config -thing-type.config.androidtv.googletv.delay.label = Delay +thing-type.config.androidtv.googletv.delay.label = Message Delay thing-type.config.androidtv.googletv.delay.description = Delay between messages -thing-type.config.androidtv.googletv.gtvEnabled.label = Enable GoogleTV -thing-type.config.androidtv.googletv.gtvEnabled.description = Enable the GoogleTV Protocol +thing-type.config.androidtv.googletv.googletvPort.label = GoogleTV Port +thing-type.config.androidtv.googletv.googletvPort.description = Port to connect to thing-type.config.androidtv.googletv.heartbeat.label = Heartbeat Frequency thing-type.config.androidtv.googletv.heartbeat.description = Frequency of heartbeats thing-type.config.androidtv.googletv.ipAddress.label = Hostname @@ -24,12 +26,34 @@ thing-type.config.androidtv.googletv.keystoreFileName.label = Keystore File Name thing-type.config.androidtv.googletv.keystoreFileName.description = Java keystore containing key and certs thing-type.config.androidtv.googletv.keystorePassword.label = Keystore Password thing-type.config.androidtv.googletv.keystorePassword.description = Password for the keystore file -thing-type.config.androidtv.googletv.googletvPort.label = GoogleTV Port -thing-type.config.androidtv.googletv.googletvPort.description = Port to connect to thing-type.config.androidtv.googletv.reconnect.label = Reconnect Delay thing-type.config.androidtv.googletv.reconnect.description = Delay between reconnection attempts +thing-type.config.androidtv.philipstv.delay.label = Delay +thing-type.config.androidtv.philipstv.delay.description = Delay between messages +thing-type.config.androidtv.philipstv.googletvPort.label = GoogleTV Port +thing-type.config.androidtv.philipstv.googletvPort.description = Port to connect to +thing-type.config.androidtv.philipstv.gtvEnabled.label = Enable GoogleTV +thing-type.config.androidtv.philipstv.gtvEnabled.description = Enable the GoogleTV Protocol +thing-type.config.androidtv.philipstv.heartbeat.label = Heartbeat Frequency +thing-type.config.androidtv.philipstv.heartbeat.description = Frequency of heartbeats +thing-type.config.androidtv.philipstv.ipAddress.label = Hostname +thing-type.config.androidtv.philipstv.ipAddress.description = Hostname or IP address of the device +thing-type.config.androidtv.philipstv.keystoreFileName.label = Keystore File Name +thing-type.config.androidtv.philipstv.keystoreFileName.description = Java keystore containing key and certs +thing-type.config.androidtv.philipstv.keystorePassword.label = Keystore Password +thing-type.config.androidtv.philipstv.keystorePassword.description = Password for the keystore file +thing-type.config.androidtv.philipstv.philipstvPort.label = PhilipsTV Port +thing-type.config.androidtv.philipstv.philipstvPort.description = Port to connect to +thing-type.config.androidtv.philipstv.reconnect.label = Reconnect Delay +thing-type.config.androidtv.philipstv.reconnect.description = Delay between reconnection attempts +thing-type.config.androidtv.philipstv.refreshRate.label = Refresh Rate +thing-type.config.androidtv.philipstv.refreshRate.description = How often the Philips TV status details get refreshed. Value in seconds. '0' deactives refreshing. +thing-type.config.androidtv.philipstv.useUpnpDiscovery.label = Use UPnP Discovery +thing-type.config.androidtv.philipstv.useUpnpDiscovery.description = Enables UPnP Discovery. If disabled, constant HTTPS polling will happen. thing-type.config.androidtv.shieldtv.delay.label = Delay thing-type.config.androidtv.shieldtv.delay.description = Delay between messages +thing-type.config.androidtv.shieldtv.googletvPort.label = GoogleTV Port +thing-type.config.androidtv.shieldtv.googletvPort.description = Port to connect to thing-type.config.androidtv.shieldtv.gtvEnabled.label = Enable GoogleTV thing-type.config.androidtv.shieldtv.gtvEnabled.description = Enable the GoogleTV Protocol thing-type.config.androidtv.shieldtv.heartbeat.label = Heartbeat Frequency @@ -40,21 +64,65 @@ thing-type.config.androidtv.shieldtv.keystoreFileName.label = Keystore File Name thing-type.config.androidtv.shieldtv.keystoreFileName.description = Java keystore containing key and certs thing-type.config.androidtv.shieldtv.keystorePassword.label = Keystore Password thing-type.config.androidtv.shieldtv.keystorePassword.description = Password for the keystore file -thing-type.config.androidtv.shieldtv.googletvPort.label = GoogleTV Port -thing-type.config.androidtv.shieldtv.googletvPort.description = Port to connect to -thing-type.config.androidtv.shieldtv.shieldtvPort.label = ShieldTV Port -thing-type.config.androidtv.shieldtv.shieldtvPort.description = Port to connect to thing-type.config.androidtv.shieldtv.reconnect.label = Reconnect Delay thing-type.config.androidtv.shieldtv.reconnect.description = Delay between reconnection attempts +thing-type.config.androidtv.shieldtv.shieldtvPort.label = ShieldTV Port +thing-type.config.androidtv.shieldtv.shieldtvPort.description = Port to connect to # channel types +channel-type.androidtv.ambilightBottomColor.label = Bottom Ambilight +channel-type.androidtv.ambilightBottomColor.description = Sets the Ambilight color for the bottom. +channel-type.androidtv.ambilightColor.label = All Ambilight +channel-type.androidtv.ambilightColor.description = Sets the Ambilight color for all sides. +channel-type.androidtv.ambilightHuePower.label = Ambilight + Hue Power +channel-type.androidtv.ambilightHuePower.description = Ambilight + Hue power. Turns ambilight with connected Philips Hue Lamps on or off. +channel-type.androidtv.ambilightLeftColor.label = Left Ambilight +channel-type.androidtv.ambilightLeftColor.description = Sets the Ambilight color for the left side. +channel-type.androidtv.ambilightLoungePower.label = Ambilight Lounge Power +channel-type.androidtv.ambilightLoungePower.description = Ambilight lounge power. Turns ambilight lounge on or off. +channel-type.androidtv.ambilightPower.label = Ambilight Power +channel-type.androidtv.ambilightPower.description = Ambilight power. Turns ambilight on or off. +channel-type.androidtv.ambilightRightColor.label = Right Ambilight +channel-type.androidtv.ambilightRightColor.description = Sets the Ambilight color for the right side. +channel-type.androidtv.ambilightStyle.label = Ambilight Style +channel-type.androidtv.ambilightStyle.description = Current ambilight style. Changing this to a value from the List, switches the ambilight style. +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ STANDARD = FOLLOW_VIDEO STANDARD +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ NATURAL = FOLLOW_VIDEO NATURAL +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ IMMERSIVE = FOLLOW_VIDEO IMMERSIVE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ VIVID = FOLLOW_VIDEO VIVID +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ GAME = FOLLOW_VIDEO GAME +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ COMFORT = FOLLOW_VIDEO COMFORT +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ RELAX = FOLLOW_VIDEO RELAX +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ ENERGY_ADAPTIVE_BRIGHTNESS = FOLLOW_AUDIO ENERGY_ADAPTIVE_BRIGHTNESS +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ ENERGY_ADAPTIVE_COLORS = FOLLOW_AUDIO ENERGY_ADAPTIVE_COLORS +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ VU_METER = FOLLOW_AUDIO VU_METER +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ SPECTRUM_ANALYZER = FOLLOW_AUDIO SPECTRUM_ANALYZER +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ KNIGHT_RIDER_CLOCKWISE = FOLLOW_AUDIO KNIGHT_RIDER_CLOCKWISE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ KNIGHT_RIDER_ALTERNATING = FOLLOW_AUDIO KNIGHT_RIDER_ALTERNATING +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ RANDOM_PIXEL_FLASH = FOLLOW_AUDIO RANDOM_PIXEL_FLASH +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ PARTY = FOLLOW_AUDIO PARTY +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ MODE_RANDOM = FOLLOW_AUDIO MODE_RANDOM +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ MANUAL_HUE = FOLLOW_COLOR MANUAL_HUE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ HOT_LAVA = FOLLOW_COLOR HOT_LAVA +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ DEEP_WATER = FOLLOW_COLOR DEEP_WATER +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ FRESH_NATURE = FOLLOW_COLOR FRESH_NATURE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ ISF = FOLLOW_COLOR ISF +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ PTA_LOUNGE = FOLLOW_COLOR PTA_LOUNGE +channel-type.androidtv.ambilightTopColor.label = Top Ambilight +channel-type.androidtv.ambilightTopColor.description = Sets the Ambilight color for the top. channel-type.androidtv.app.label = App channel-type.androidtv.app.description = App Control +channel-type.androidtv.appicon.label = App Icon +channel-type.androidtv.appicon.description = App Icon channel-type.androidtv.appname.label = App Name channel-type.androidtv.appname.description = App Name channel-type.androidtv.appurl.label = App URL channel-type.androidtv.appurl.description = App URL +channel-type.androidtv.brightness.label = Brightness +channel-type.androidtv.brightness.description = Brightness of the TV picture. +channel-type.androidtv.contrast.label = Contrast +channel-type.androidtv.contrast.description = Contrast of the TV picture. channel-type.androidtv.debug.label = DEBUG Command channel-type.androidtv.debug.description = Binding control (for debugging) channel-type.androidtv.keyboard.label = Keyboard @@ -67,6 +135,17 @@ channel-type.androidtv.pincode.label = Pin Code channel-type.androidtv.pincode.description = Send Pin Code channel-type.androidtv.player.label = Player channel-type.androidtv.player.description = Player Control +channel-type.androidtv.searchContent.label = Search Content +channel-type.androidtv.searchContent.description = Keyword(s) to search for on TV via Google Assistant +channel-type.androidtv.sharpness.label = Sharpness +channel-type.androidtv.sharpness.description = Sharpness of the TV picture. +channel-type.androidtv.tvChannel.label = TV Channel +channel-type.androidtv.tvChannel.description = Name of the currently running TV Channel. Changing this to a value from the List, switches the channel. + +# thing types config + +thing-type.config.androidtv.googletv.gtvEnabled.label = Enable GoogleTV +thing-type.config.androidtv.googletv.gtvEnabled.description = Enable the GoogleTV Protocol # custom thing status @@ -86,5 +165,18 @@ offline.interrupted = Interrupted offline.io-error = I/O Error offline.runtime-exception = Runtime exception offline.user-forced-pin-process = User Forced PIN Process +offline.error-occured-while-presenting-pairing-code = Error occurred while trying to present a Pairing Code on TV +offline.error-occured-during-retrieval-of-credentials = Error occurred during retrieval of credentials +offline.pairing-is-not-configured-yet = Pairing is not configured yet +offline.error-occurred-while-trying-to-present-a-pairing-code-on-tv = Error occurred while trying to present a Pairing Code on TV +offline.pairing-code-is-available-but-credentials-missing = Pairing Code is available, but credentials missing. Trying to retrieve them +offline.error-occurred-during-retrieval-of-credentials = Error occurred during retrieval of credentials +offline.pairing-was-unsuccessful = Pairing was unsuccessful +offline.error-occurred-during-creation-of-http-client = Error occurred during creation of HTTP client +offline.authentication-with-philips-tv-device-was-successful-continuing-initialization-of-the-tv = Authentication with Philips TV device was successful. Continuing initialization of the tv. +offline.could-not-successfully-finish-pairing-process-with-the-tv = Could not successfully finish pairing process with the TV +offline.tv-is-not-reachable-and-should-therefore-be-off = TV is not reachable and should therefore be off +offline.tv-does-not-accept-commands-at-the-moment = TV does not accept commands at the moment offline.unknown = Unknown +online.standby = Standby online.online = Online diff --git a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml index 8e5c032c0dadd..9fd3c643df189 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml @@ -157,6 +157,106 @@ 5 true + + + Delay between messages + 0 + true + + + + + + + + A Philips TV device + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + network-address + + Hostname or IP address of the device + + + + Port to connect to + 6466 + true + + + + Port to connect to + 1926 + true + + + + + How often the Philips TV status details get refreshed. Value in seconds. '0' deactives refreshing. + + true + 10 + + + + + Enables UPnP Discovery. If disabled, constant HTTPS polling will happen. + + true + true + + + + Java keystore containing key and certs + true + + + password + + Password for the keystore file + true + + + + Delay between reconnection attempts + 60 + true + + + + Frequency of heartbeats + 5 + true + Delay between messages @@ -197,6 +297,12 @@ App URL + + Image + + App Icon + + String @@ -227,4 +333,131 @@ Player Control + + String + + Name of the currently running TV Channel. Changing this to a value from the List, switches the + channel. + + + + + String + + Keyword(s) to search for on TV via Google Assistant + + + + Switch + + Ambilight power. Turns ambilight on or off. + Ambilight + + + + Switch + + Ambilight + Hue power. Turns ambilight with connected Philips Hue Lamps on or off. + Ambilight + + + + Switch + + Ambilight lounge power. Turns ambilight lounge on or off. + Ambilight + + + + String + + Current ambilight style. Changing this to a value from the List, switches the ambilight style. + + Ambilight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Color + + Sets the Ambilight color for all sides. + Ambilight + + + + Color + + Sets the Ambilight color for the left side. + Ambilight + + + + Color + + Sets the Ambilight color for the right side. + Ambilight + + + + Color + + Sets the Ambilight color for the top. + Ambilight + + + + Color + + Sets the Ambilight color for the bottom. + Ambilight + + + + Dimmer + + Brightness of the TV picture. + Tv Picture + + + + Dimmer + + Contrast of the TV picture. + Tv Picture + + + + Dimmer + + Sharpness of the TV picture. + Tv Picture + + From a2899177b23e4a2c7829f1cf8f8ee281bca210d7 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Fri, 9 Feb 2024 22:52:43 +0100 Subject: [PATCH 07/13] Re-adding missing Plus10V to list of supported things (#16391) Signed-off-by: Markus Michels --- .../openhab/binding/shelly/internal/ShellyBindingConstants.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index 04dee188bdef1..ba0fd6b0eea9f 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -75,6 +75,7 @@ public class ShellyBindingConstants { THING_TYPE_SHELLYPLUS2PM_ROLLER, // THING_TYPE_SHELLYPLUSI4, // THING_TYPE_SHELLYPLUSI4DC, // + THING_TYPE_SHELLYPLUSDIMMER10V, // THING_TYPE_SHELLYPLUSHT, // THING_TYPE_SHELLYPLUSSMOKE, // THING_TYPE_SHELLYPLUSPLUGS, // From 78ba7e895646bda93d51768d38caf997b23a6e25 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Fri, 9 Feb 2024 23:08:21 +0100 Subject: [PATCH 08/13] New translations androidtv.properties (Italian) (#16390) --- .../OH-INF/i18n/androidtv_it.properties | 110 ++++++++++++++++-- 1 file changed, 101 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv_it.properties b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv_it.properties index d040ab594f2bd..dcc21a6da1fa0 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv_it.properties +++ b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv_it.properties @@ -7,15 +7,17 @@ addon.androidtv.description = Questo è l'add-on per AndroidTV. thing-type.androidtv.googletv.label = GoogleTV thing-type.androidtv.googletv.description = GoogleTV +thing-type.androidtv.philipstv.label = TV Philips +thing-type.androidtv.philipstv.description = Un dispositivo Philips TV thing-type.androidtv.shieldtv.label = ShieldTV thing-type.androidtv.shieldtv.description = Nvidia ShieldTV # thing types config -thing-type.config.androidtv.googletv.delay.label = Ritardo +thing-type.config.androidtv.googletv.delay.label = Ritardo Messaggio thing-type.config.androidtv.googletv.delay.description = Ritardo tra i messaggi -thing-type.config.androidtv.googletv.gtvEnabled.label = Abilita GoogleTV -thing-type.config.androidtv.googletv.gtvEnabled.description = Abilita il protocollo GoogleTV +thing-type.config.androidtv.googletv.googletvPort.label = Porta GoogleTV +thing-type.config.androidtv.googletv.googletvPort.description = Porta a cui connettersi thing-type.config.androidtv.googletv.heartbeat.label = Frequenza Hearthbeat thing-type.config.androidtv.googletv.heartbeat.description = Frequenza del hearthbeat thing-type.config.androidtv.googletv.ipAddress.label = Hostname @@ -24,12 +26,34 @@ thing-type.config.androidtv.googletv.keystoreFileName.label = Nome File Keystore thing-type.config.androidtv.googletv.keystoreFileName.description = Keystore Java contenente chiave e certificati thing-type.config.androidtv.googletv.keystorePassword.label = Password Keystore thing-type.config.androidtv.googletv.keystorePassword.description = Password per il file keystore -thing-type.config.androidtv.googletv.googletvPort.label = Porta GoogleTV -thing-type.config.androidtv.googletv.googletvPort.description = Porta a cui connettersi thing-type.config.androidtv.googletv.reconnect.label = Ritardo Riconnessione thing-type.config.androidtv.googletv.reconnect.description = Ritardo tra i tentativi di riconnessione +thing-type.config.androidtv.philipstv.delay.label = Ritardo +thing-type.config.androidtv.philipstv.delay.description = Ritardo tra i messaggi +thing-type.config.androidtv.philipstv.googletvPort.label = Porta GoogleTV +thing-type.config.androidtv.philipstv.googletvPort.description = Porta a cui connettersi +thing-type.config.androidtv.philipstv.gtvEnabled.label = Abilita GoogleTV +thing-type.config.androidtv.philipstv.gtvEnabled.description = Abilita il protocollo GoogleTV +thing-type.config.androidtv.philipstv.heartbeat.label = Frequenza Hearthbeat +thing-type.config.androidtv.philipstv.heartbeat.description = Frequenza del hearthbeat +thing-type.config.androidtv.philipstv.ipAddress.label = Nome Host +thing-type.config.androidtv.philipstv.ipAddress.description = Nome host o indirizzo IP del dispositivo +thing-type.config.androidtv.philipstv.keystoreFileName.label = Nome File Keystore +thing-type.config.androidtv.philipstv.keystoreFileName.description = Keystore Java contenente chiave e certificati +thing-type.config.androidtv.philipstv.keystorePassword.label = Password Keystore +thing-type.config.androidtv.philipstv.keystorePassword.description = Password per il file keystore +thing-type.config.androidtv.philipstv.philipstvPort.label = Porta PhilipsTV +thing-type.config.androidtv.philipstv.philipstvPort.description = Porta a cui connettersi +thing-type.config.androidtv.philipstv.reconnect.label = Ritardo Riconnessione +thing-type.config.androidtv.philipstv.reconnect.description = Ritardo tra i tentativi di riconnessione +thing-type.config.androidtv.philipstv.refreshRate.label = Frequenza Di Aggiornamento +thing-type.config.androidtv.philipstv.refreshRate.description = Quante volte i dettagli dello stato della TV Philips vengono aggiornati. Valore in secondi. '0' disattiva l'aggiornamento. +thing-type.config.androidtv.philipstv.useUpnpDiscovery.label = Usa UPnP Discovery +thing-type.config.androidtv.philipstv.useUpnpDiscovery.description = Abilita la scoperta di UPnP. Se disabilitata, si verificherà un sondaggio costante di HTTPS. thing-type.config.androidtv.shieldtv.delay.label = Ritardo thing-type.config.androidtv.shieldtv.delay.description = Ritardo tra i messaggi +thing-type.config.androidtv.shieldtv.googletvPort.label = Porta GoogleTV +thing-type.config.androidtv.shieldtv.googletvPort.description = Porta a cui connettersi thing-type.config.androidtv.shieldtv.gtvEnabled.label = Abilita GoogleTV thing-type.config.androidtv.shieldtv.gtvEnabled.description = Abilita il protocollo GoogleTV thing-type.config.androidtv.shieldtv.heartbeat.label = Frequenza Hearthbeat @@ -40,21 +64,65 @@ thing-type.config.androidtv.shieldtv.keystoreFileName.label = Nome File Keystore thing-type.config.androidtv.shieldtv.keystoreFileName.description = Keystore Java contenente chiave e certificati thing-type.config.androidtv.shieldtv.keystorePassword.label = Password Keystore thing-type.config.androidtv.shieldtv.keystorePassword.description = Password per il file keystore -thing-type.config.androidtv.shieldtv.googletvPort.label = Porta GoogleTV -thing-type.config.androidtv.shieldtv.googletvPort.description = Porta a cui connettersi -thing-type.config.androidtv.shieldtv.shieldtvPort.label = Porta ShieldTV -thing-type.config.androidtv.shieldtv.shieldtvPort.description = Porta a cui connettersi thing-type.config.androidtv.shieldtv.reconnect.label = Ritardo Riconnessione thing-type.config.androidtv.shieldtv.reconnect.description = Ritardo tra i tentativi di riconnessione +thing-type.config.androidtv.shieldtv.shieldtvPort.label = Porta ShieldTV +thing-type.config.androidtv.shieldtv.shieldtvPort.description = Porta a cui connettersi # channel types +channel-type.androidtv.ambilightBottomColor.label = Ambilight In Basso +channel-type.androidtv.ambilightBottomColor.description = Imposta il colore Ambilight per il basso. +channel-type.androidtv.ambilightColor.label = Tutto Ambilight +channel-type.androidtv.ambilightColor.description = Imposta il colore Ambilight per tutti i lati. +channel-type.androidtv.ambilightHuePower.label = Accensione Ambilight Lounge +channel-type.androidtv.ambilightHuePower.description = Alimentazione Hue Ambilight. Accende o disattiva le lampade Philips Hue collegate. +channel-type.androidtv.ambilightLeftColor.label = Ambilight Sinistro +channel-type.androidtv.ambilightLeftColor.description = Imposta il colore Ambilight per il lato sinistro. +channel-type.androidtv.ambilightLoungePower.label = Accensione Ambilight Lounge +channel-type.androidtv.ambilightLoungePower.description = Accensione Ambilight Lounge. Attiva o disattiva la lounge ambilight. +channel-type.androidtv.ambilightPower.label = Accensione Ambilight +channel-type.androidtv.ambilightPower.description = Accensione Ambilight. Accende o spegne l'ambilight. +channel-type.androidtv.ambilightRightColor.label = Ambilight Destro +channel-type.androidtv.ambilightRightColor.description = Imposta il colore Ambilight per il lato destro. +channel-type.androidtv.ambilightStyle.label = Stile Ambilight +channel-type.androidtv.ambilightStyle.description = Lo stile ambilight attuale. Cambiandolo in un valore dalla lista, cambia lo stile ambilight. +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ STANDARD = FOLLOW_VIDEO STANDARD +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ NATURAL = FOLLOW_VIDEO NATURAL +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ IMMERSIVE = FOLLOW_VIDEO IMMERSIVE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ VIVID = FOLLOW_VIDEO VIVID +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ GAME = FOLLOW_VIDEO GAME +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ COMFORT = FOLLOW_VIDEO COMFORT +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ RELAX = FOLLOW_VIDEO RELAX +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ ENERGY_ADAPTIVE_BRIGHTNESS = FOLLOW_AUDIO ENERGY_ADAPTIVE_BRIGHTNESS +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ ENERGY_ADAPTIVE_COLORS = FOLLOW_AUDIO ENERGY_ADAPTIVE_COLORS +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ VU_METER = FOLLOW_AUDIO VU_METER +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ SPECTRUM_ANALYZER = FOLLOW_AUDIO SPECTRUM_ANALYZER +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ KNIGHT_RIDER_CLOCKWISE = FOLLOW_AUDIO KNIGHT_RIDER_CLOCKWISE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ KNIGHT_RIDER_ALTERNATING = FOLLOW_AUDIO KNIGHT_RIDER_ALTERNATING +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ RANDOM_PIXEL_FLASH = FOLLOW_AUDIO RANDOM_PIXEL_FLASH +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ PARTY = FOLLOW_AUDIO PARTY +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ MODE_RANDOM = FOLLOW_AUDIO MODE_RANDOM +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ MANUAL_HUE = FOLLOW_COLOR MANUAL_HUE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ HOT_LAVA = FOLLOW_COLOR HOT_LAVA +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ DEEP_WATER = FOLLOW_COLOR DEEP_WATER +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ FRESH_NATURE = FOLLOW_COLOR FRESH_NATURE +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ ISF = FOLLOW_COLOR ISF +channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ PTA_LOUNGE = FOLLOW_COLOR PTA_LOUNGE +channel-type.androidtv.ambilightTopColor.label = Ambilight Superiore +channel-type.androidtv.ambilightTopColor.description = Imposta il colore Ambilight per la parte superiore. channel-type.androidtv.app.label = App channel-type.androidtv.app.description = Controllo App +channel-type.androidtv.appicon.label = Icona App +channel-type.androidtv.appicon.description = Icona App channel-type.androidtv.appname.label = Nome App channel-type.androidtv.appname.description = Nome App channel-type.androidtv.appurl.label = Url App channel-type.androidtv.appurl.description = Url App +channel-type.androidtv.brightness.label = Luminosità +channel-type.androidtv.brightness.description = Luminosità dell'immagine TV. +channel-type.androidtv.contrast.label = Contrasto +channel-type.androidtv.contrast.description = Contrasto dell'immagine TV. channel-type.androidtv.debug.label = Comando DEBUG channel-type.androidtv.debug.description = Controllo Binding (per debug) channel-type.androidtv.keyboard.label = Tastiera @@ -67,6 +135,17 @@ channel-type.androidtv.pincode.label = Codice Pin channel-type.androidtv.pincode.description = Invia Codice Pin channel-type.androidtv.player.label = Lettore channel-type.androidtv.player.description = Controlli Riproduttore +channel-type.androidtv.searchContent.label = Cerca Contenuto +channel-type.androidtv.searchContent.description = Parole chiave da cercare in TV tramite Google Assistant +channel-type.androidtv.sharpness.label = Nitidezza +channel-type.androidtv.sharpness.description = Nitidezza dell'immagine TV. +channel-type.androidtv.tvChannel.label = Canale TV +channel-type.androidtv.tvChannel.description = Nome del canale TV attualmente in esecuzione. Cambiandolo in un valore dalla lista, cambia il canale. + +# thing types config + +thing-type.config.androidtv.googletv.gtvEnabled.label = Abilita GoogleTV +thing-type.config.androidtv.googletv.gtvEnabled.description = Abilita il protocollo GoogleTV # custom thing status @@ -86,5 +165,18 @@ offline.interrupted = Interrotto offline.io-error = Errore I/O offline.runtime-exception = Eccezione di esecuzione offline.user-forced-pin-process = Processo Pin Forzato Dall'Utente +offline.error-occured-while-presenting-pairing-code = Si è verificato un errore durante il tentativo di presentare un codice di accoppiamento sulla TV +offline.error-occured-during-retrieval-of-credentials = Si è verificato un errore durante il recupero delle credenziali +offline.pairing-is-not-configured-yet = L'accoppiamento non è ancora configurato +offline.error-occurred-while-trying-to-present-a-pairing-code-on-tv = Si è verificato un errore durante il tentativo di presentare un codice di accoppiamento sulla TV +offline.pairing-code-is-available-but-credentials-missing = Il codice di accoppiamento è disponibile, ma mancano le credenziali. Tento di recuperarli +offline.error-occurred-during-retrieval-of-credentials = Si è verificato un errore durante il recupero delle credenziali +offline.pairing-was-unsuccessful = Accoppiamento non riuscito +offline.error-occurred-during-creation-of-http-client = Errore durante la creazione del client HTTP +offline.authentication-with-philips-tv-device-was-successful-continuing-initialization-of-the-tv = L'autenticazione con il dispositivo Philips TV ha avuto successo. Continuare l'inizializzazione del tv. +offline.could-not-successfully-finish-pairing-process-with-the-tv = Impossibile terminare con successo il processo di accoppiamento con la TV +offline.tv-is-not-reachable-and-should-therefore-be-off = La TV non è raggiungibile e dovrebbe quindi essere spenta +offline.tv-does-not-accept-commands-at-the-moment = La TV non accetta comandi al momento offline.unknown = Sconosciuto +online.standby = In standby online.online = Online From d1caa31d6a4763f9836ae74d387acda78d9bb984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20T=C3=B3th?= Date: Sat, 10 Feb 2024 15:17:43 +0100 Subject: [PATCH 09/13] [solax] Add support for Solax X3MIC / G2 inverter and workmode channel (#16248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support for Solax X3MIC / G2 inverter, and workmode channel for the existing X1, X3 also * Add update instructions and make raise the target version to 1 * Refactor the workmode to be enum instead of string constants Signed-off-by: Henrik Tóth Signed-off-by: Konstantin Polihronov Signed-off-by: Leo Siepel --- .../solax/internal/SolaxBindingConstants.java | 3 + .../internal/SolaxLocalAccessHandler.java | 6 + .../solax/internal/model/InverterData.java | 16 ++ .../solax/internal/model/InverterType.java | 3 +- .../model/impl/X1HybridG4InverterData.java | 5 + .../model/impl/X3HybridG4InverterData.java | 5 + .../model/impl/X3MicOrProG2InverterData.java | 153 ++++++++++++++++++ .../model/parsers/X1HybridG4DataParser.java | 2 +- .../model/parsers/X3HybridG4DataParser.java | 2 +- .../model/parsers/X3MicOrProG2DataParser.java | 55 +++++++ .../resources/OH-INF/i18n/solax.properties | 36 ++++- .../resources/OH-INF/thing/channel_types.xml | 33 ++++ .../OH-INF/thing/localConnectInverter.xml | 27 +++- .../local_connect_inverter_type_update.xml | 19 +++ .../solax/internal/TestX1HybridG4Parser.java | 3 + .../solax/internal/TestX3HybridG4Parser.java | 3 + .../internal/TestX3MicOrProG2Parser.java | 97 +++++++++++ 17 files changed, 452 insertions(+), 16 deletions(-) create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java create mode 100644 bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java index e04633e377b89..39d0b3ba6eb67 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java @@ -61,6 +61,9 @@ public class SolaxBindingConstants { public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1 = "inverter-frequency-phase1"; public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2 = "inverter-frequency-phase2"; public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3 = "inverter-frequency-phase3"; + public static final String CHANNEL_INVERTER_TEMPERATURE1 = "inverter-temperature1"; + public static final String CHANNEL_INVERTER_TEMPERATURE2 = "inverter-temperature2"; + public static final String CHANNEL_INVERTER_WORKMODE = "inverter-workmode"; // Generic public static final String CHANNEL_INVERTER_PV1_POWER = "pv1-power"; diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java index 94b418f3651b8..dd17e7bc27b36 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java @@ -190,12 +190,18 @@ private void updateChannels(RawDataParser parser, InverterData inverterData) { supportedChannels); updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(), SIUnits.CELSIUS, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE1, inverterData.getInverterTemperature1(), + SIUnits.CELSIUS, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE2, inverterData.getInverterTemperature2(), + SIUnits.CELSIUS, supportedChannels); updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(), Units.PERCENT, supportedChannels); updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT, supportedChannels); updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT, supportedChannels); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE, + new StringType(inverterData.getInverterWorkMode())); // Totals updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR, diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java index 04c60aed7b4a0..429e336781932 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java @@ -82,6 +82,22 @@ default short getBatteryTemperature() { return Short.MIN_VALUE; } + default short getInverterTemperature1() { + return Short.MIN_VALUE; + } + + default short getInverterTemperature2() { + return Short.MIN_VALUE; + } + + default short getInverterWorkModeCode() { + return Short.MIN_VALUE; + } + + default String getInverterWorkMode() { + return String.valueOf(getInverterWorkModeCode()); + } + default short getBatteryLevel() { return Short.MIN_VALUE; } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java index 072a0e9abbdcc..9d87543dfe721 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java @@ -21,6 +21,7 @@ import org.openhab.binding.solax.internal.model.parsers.RawDataParser; import org.openhab.binding.solax.internal.model.parsers.X1HybridG4DataParser; import org.openhab.binding.solax.internal.model.parsers.X3HybridG4DataParser; +import org.openhab.binding.solax.internal.model.parsers.X3MicOrProG2DataParser; /** * The {@link InverterType} class is enum representing the different inverter types with a simple logic to convert from @@ -46,7 +47,7 @@ public enum InverterType { J1_ESS(13), X3_HYBRID_G4(14, new X3HybridG4DataParser()), X1_HYBRID_G4(15, new X1HybridG4DataParser()), - X3_MIC_OR_PRO_G2(16), + X3_MIC_OR_PRO_G2(16, new X3MicOrProG2DataParser()), X1_SPT(17), X1_BOOST_OR_MINI_G4(18), A1_HYB_G2(19), diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java index 238176b047269..ead4e05302402 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java @@ -83,6 +83,11 @@ public short getPV2Power() { return getData(9); } + @Override + public short getInverterWorkModeCode() { + return getData(10); + } + @Override public double getBatteryVoltage() { return ((double) getData(14)) / 100; diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java index 92f57373f1c1f..48ec4b707bd1f 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java @@ -125,6 +125,11 @@ public double getFrequencyPhase3() { return ((double) getData(18)) / 100; } + @Override + public short getInverterWorkModeCode() { + return getData(19); + } + // Battery @Override diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java new file mode 100644 index 0000000000000..ddf6fb379cc01 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.impl; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; + +/** + * The {@link X3HybridG4InverterData} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Henrik Tóth - Initial contribution + * (based on X1/X3 G4 parser from Konstantin Polihronov) + */ +@NonNullByDefault +public class X3MicOrProG2InverterData extends CommonInverterData { + + public X3MicOrProG2InverterData(LocalConnectRawDataBean data) { + super(data); + } + + // Inverter data + + @Override + public double getVoltagePhase1() { + return ((double) getData(0)) / 10; + } + + @Override + public double getVoltagePhase2() { + return ((double) getData(1)) / 10; + } + + @Override + public double getVoltagePhase3() { + return ((double) getData(2)) / 10; + } + + @Override + public double getCurrentPhase1() { + return ((double) getData(3)) / 10; + } + + @Override + public double getCurrentPhase2() { + return ((double) getData(4)) / 10; + } + + @Override + public double getCurrentPhase3() { + return ((double) getData(5)) / 10; + } + + @Override + public short getOutputPowerPhase1() { + return getData(6); + } + + @Override + public short getOutputPowerPhase2() { + return getData(7); + } + + @Override + public short getOutputPowerPhase3() { + return getData(8); + } + + @Override + public double getPV1Voltage() { + return ((double) getData(9)) / 10; + } + + @Override + public double getPV2Voltage() { + return ((double) getData(10)) / 10; + } + + @Override + public double getPV1Current() { + return ((double) getData(12)) / 10; + } + + @Override + public double getPV2Current() { + return ((double) getData(13)) / 10; + } + + @Override + public short getPV1Power() { + return getData(15); + } + + @Override + public short getPV2Power() { + return getData(16); + } + + @Override + public double getFrequencyPhase1() { + return ((double) getData(18)) / 100; + } + + @Override + public double getFrequencyPhase2() { + return ((double) getData(19)) / 100; + } + + @Override + public double getFrequencyPhase3() { + return ((double) getData(20)) / 100; + } + + @Override + public short getInverterWorkModeCode() { + return getData(21); + } + + @Override + public double getTotalEnergy() { + return ((double) getData(22)) / 10; + } + + @Override + public double getTodayEnergy() { + return ((double) getData(24)) / 10; + } + + @Override + public short getInverterTemperature1() { + return getData(26); + } + + @Override + public short getInverterTemperature2() { + return getData(27); + } + + @Override + public short getTotalOutputPower() { + return getData(78); + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java index fd68cf15e3289..538e937231f6a 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java @@ -36,7 +36,7 @@ public class X1HybridG4DataParser implements RawDataParser { CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT, CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP, CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER, CHANNEL_INVERTER_OUTPUT_CURRENT, - CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY); + CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE); @Override public InverterData getData(LocalConnectRawDataBean rawData) { diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java index 4c814feade273..7f0680aa489ea 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java @@ -44,7 +44,7 @@ public class X3HybridG4DataParser implements RawDataParser { CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_POWER_USAGE, CHANNEL_TOTAL_ENERGY, CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, CHANNEL_TOTAL_PV_ENERGY, CHANNEL_TOTAL_CONSUMPTION, CHANNEL_TODAY_ENERGY, CHANNEL_TODAY_FEED_IN_ENERGY, CHANNEL_TODAY_CONSUMPTION, - CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY); + CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE); @Override public InverterData getData(LocalConnectRawDataBean rawData) { diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java new file mode 100644 index 0000000000000..6a1660b1b4f49 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.parsers; + +import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.impl.X3MicOrProG2InverterData; + +/** + * The {@link X3MicOrProG2DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the + * X3 Mic / Pro G2 inverter. + * + * @author Henrik Tóth - Initial contribution + * (based on X1/X3 G4 parser from Konstantin Polihronov) + */ +@NonNullByDefault +public class X3MicOrProG2DataParser implements RawDataParser { + + private static final Set X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS = Set.of( + CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, + CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, + CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, + CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, + CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV2_VOLTAGE, + CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV1_POWER, + CHANNEL_INVERTER_PV2_POWER, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, + CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_TOTAL_ENERGY, + CHANNEL_TODAY_ENERGY, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, CHANNEL_INVERTER_TEMPERATURE1, + CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA); + + @Override + public InverterData getData(LocalConnectRawDataBean rawData) { + return new X3MicOrProG2InverterData(rawData); + } + + @Override + public Set getSupportedChannels() { + return X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties index 518f83a8399be..7344060f39424 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties @@ -27,12 +27,12 @@ thing-type.solax.local-connect-inverter.channel.inverter-current-phase2.label = thing-type.solax.local-connect-inverter.channel.inverter-current-phase2.description = Current to/from the inverter phase 2 thing-type.solax.local-connect-inverter.channel.inverter-current-phase3.label = Inverter Input/Output Current Phase 3 thing-type.solax.local-connect-inverter.channel.inverter-current-phase3.description = Current to/from the inverter phase 3 -thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.label = Inverter Voltage Phase 3 -thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.description = Voltage of the inverter's phase 3 -thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.label = Inverter Voltage Phase 2 -thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.description = Voltage of the inverter's phase 3 -thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.label = Inverter Voltage Phase 3 -thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.label = Inverter Frequency Phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.description = Frequency of the inverter's phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.label = Inverter Frequency Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.description = Frequency of the inverter's phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.label = Inverter Frequency Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.description = Frequency of the inverter's phase 3 thing-type.solax.local-connect-inverter.channel.inverter-output-power.label = Inverter Input/Output Power thing-type.solax.local-connect-inverter.channel.inverter-output-power.description = Power to/from the inverter thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase1.label = Inverter Input/Output Power Phase 1 @@ -41,6 +41,10 @@ thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase2.lab thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase2.description = Power to/from the inverter phase 2 thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase3.label = Inverter Input/Output Power Phase 3 thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase3.description = Power to/from the inverter phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-temperature1.label = Inverter Temperature 1 +thing-type.solax.local-connect-inverter.channel.inverter-temperature1.description = Temperature 1 of the inverter +thing-type.solax.local-connect-inverter.channel.inverter-temperature2.label = Inverter Temperature 2 +thing-type.solax.local-connect-inverter.channel.inverter-temperature2.description = Temperature 2 of the inverter thing-type.solax.local-connect-inverter.channel.inverter-total-output-power.label = Inverter Input/Output Total Power thing-type.solax.local-connect-inverter.channel.inverter-total-output-power.description = Power to/from the inverter on all phases thing-type.solax.local-connect-inverter.channel.inverter-voltage.label = Inverter Voltage @@ -51,6 +55,8 @@ thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase2.label = thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase2.description = Voltage of the inverter's phase 2 thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase3.label = Inverter Voltage Phase 3 thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase3.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-workmode.label = Inverter Workmode +thing-type.solax.local-connect-inverter.channel.inverter-workmode.description = Inverter Workmode thing-type.solax.local-connect-inverter.channel.power-usage.label = Power Usage thing-type.solax.local-connect-inverter.channel.power-usage.description = Current power consumption of the building thing-type.solax.local-connect-inverter.channel.pv-total-current.label = PV Total Current @@ -107,6 +113,24 @@ channel-type.solax.battery-temperature.label = Battery Temperature channel-type.solax.battery-temperature.description = Battery Temperature channel-type.solax.frequency.label = Electric Frequency channel-type.solax.frequency.description = Frequency of the electricity to/from the inverter +channel-type.solax.inverter-temperature.label = Inverter Temperature +channel-type.solax.inverter-temperature.description = Inverter Temperature +channel-type.solax.inverter-workmode.label = Inverter Workmode +channel-type.solax.inverter-workmode.description = Inverter Workmode +channel-type.solax.inverter-workmode.state.option.0 = Waiting +channel-type.solax.inverter-workmode.state.option.1 = Checking +channel-type.solax.inverter-workmode.state.option.2 = Normal +channel-type.solax.inverter-workmode.state.option.3 = Fault +channel-type.solax.inverter-workmode.state.option.4 = Permanent Fault +channel-type.solax.inverter-workmode.state.option.5 = Updating +channel-type.solax.inverter-workmode.state.option.6 = EPS Check +channel-type.solax.inverter-workmode.state.option.7 = EPS Normal +channel-type.solax.inverter-workmode.state.option.8 = Self Test +channel-type.solax.inverter-workmode.state.option.9 = Idle +channel-type.solax.inverter-workmode.state.option.10 = Standby +channel-type.solax.inverter-workmode.state.option.11 = PV Wake-up Battery +channel-type.solax.inverter-workmode.state.option.12 = GEN Check +channel-type.solax.inverter-workmode.state.option.13 = GEN Run channel-type.solax.last-retrieve-time-stamp.label = Last Retrieve Time Stamp channel-type.solax.last-retrieve-time-stamp.description = Last time with a successful retrieval of data channel-type.solax.raw-data-type.label = Raw Data diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml index a1a814436d56e..472c9eea76d00 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml @@ -24,6 +24,39 @@ + + Number:Temperature + + Inverter Temperature + + Measurement + Temperature + + + + + String + + Inverter Workmode + + + + + + + + + + + + + + + + + + + DateTime diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml index 67e4b20f18ad6..f3f10681598f2 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml @@ -67,16 +67,16 @@ Voltage of the inverter's phase 3 - - Voltage of the inverter's phase 3 + + Frequency of the inverter's phase 1 - - Voltage of the inverter's phase 3 + + Frequency of the inverter's phase 2 - - Voltage of the inverter's phase 3 + + Frequency of the inverter's phase 3 @@ -113,6 +113,19 @@ The sum of PV currents from all strings + + + Temperature 1 of the inverter + + + + Temperature 2 of the inverter + + + + Inverter Workmode + + Power to/from the battery @@ -193,7 +206,7 @@ - 1 + 2 diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml index 1a1e54416d1fb..dc79f7bebfd2c 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml @@ -130,5 +130,24 @@ Energy consumed for the day + + + + + solax:inverter-temperature + + Temperature 1 of the inverter + + + solax:inverter-temperature + + Temperature 2 of the inverter + + + solax:inverter-workmode + + Inverter Workmode + + diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java index 08def71318e17..c8c8c3ac97489 100644 --- a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java @@ -69,6 +69,9 @@ public void testParser() { assertEquals(487, data.getPV1Power()); // [8] assertEquals(65, data.getPV2Power()); // [9] + assertEquals(2, data.getInverterWorkModeCode()); // [10] + assertEquals("2", data.getInverterWorkMode()); // [10] + assertEquals(121.8, data.getBatteryVoltage()); // [14] assertEquals(5, data.getBatteryCurrent()); // [15] assertEquals(605, data.getBatteryPower()); // [16] diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java index 7bb4e972c5c2a..8c02197c80f40 100644 --- a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java @@ -89,6 +89,9 @@ public void testParser() { assertEquals(49.96, data.getFrequencyPhase2()); // [17] assertEquals(49.96, data.getFrequencyPhase3()); // [18] + assertEquals(2, data.getInverterWorkModeCode()); // [19] + assertEquals("2", data.getInverterWorkMode()); // [19] + assertEquals(-41, data.getFeedInPower()); // [34] - [35] assertEquals(313.3, data.getBatteryVoltage()); // [39] diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java new file mode 100644 index 0000000000000..c3d353d7a9408 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; + +/** + * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class TestX3MicOrProG2Parser { + + String rawData = """ + { + sn:XYZ, + ver:3.003.02, + type:16,Data:[ + 2515,2449,2484,5,5,9,54,44,20,4080,4340, + 0,1,2,0,67,102,0,4999,4999,4999,2,19035, + 0,50,8000,5,9, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, + 0,0,0,0,0,0,0,0,120,40,1,6,5,0,6772,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0 + ], + Information:[8.000,16,XY,8,1.15,0.00,1.11,1.01,0.00,1] + } + """; + + @Test + public void testParser() { + LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData); + int type = bean.getType(); + InverterType inverterType = InverterType.fromIndex(type); + assertEquals(InverterType.X3_MIC_OR_PRO_G2, inverterType, "Inverter type not recognized properly"); + + RawDataParser parser = inverterType.getParser(); + assertNotNull(parser); + + InverterData data = parser.getData(bean); + assertEquals("XYZ", data.getWifiSerial()); + assertEquals("3.003.02", data.getWifiVersion()); + + assertEquals(251.5, data.getVoltagePhase1()); // [0] + assertEquals(244.9, data.getVoltagePhase2()); // [1] + assertEquals(248.4, data.getVoltagePhase3()); // [2] + + assertEquals(0.5, data.getCurrentPhase1()); // [3] + assertEquals(0.5, data.getCurrentPhase2()); // [4] + assertEquals(0.9, data.getCurrentPhase3()); // [5] + + assertEquals(54, data.getOutputPowerPhase1()); // [6] + assertEquals(44, data.getOutputPowerPhase2()); // [7] + assertEquals(20, data.getOutputPowerPhase3()); // [8] + + assertEquals(408, data.getPV1Voltage()); // [9] + assertEquals(434, data.getPV2Voltage()); // [10] + assertEquals(0.1, data.getPV1Current()); // [12] + assertEquals(0.2, data.getPV2Current()); // [13] + assertEquals(67, data.getPV1Power()); // [15] + assertEquals(102, data.getPV2Power()); // [16] + + assertEquals(49.99, data.getFrequencyPhase1()); // [18] + assertEquals(49.99, data.getFrequencyPhase2()); // [19] + assertEquals(49.99, data.getFrequencyPhase3()); // [20] + + assertEquals(2, data.getInverterWorkModeCode()); // [21] + assertEquals("2", data.getInverterWorkMode()); // [21] + + assertEquals(5, data.getInverterTemperature1()); // [26] + assertEquals(9, data.getInverterTemperature2()); // [27] + + assertEquals(120, data.getTotalOutputPower()); // [78] + + assertEquals(1903.5, data.getTotalEnergy()); // [22] + assertEquals(5.0, data.getTodayEnergy()); // [24] + } +} From d72c5b565b4d47bf0f67f99dd556057e31e3bf01 Mon Sep 17 00:00:00 2001 From: Chiuaua79 <39282804+Chiuaua79@users.noreply.github.com> Date: Sat, 10 Feb 2024 17:03:31 +0100 Subject: [PATCH 10/13] [Meteostick] Add windvane calibration (#16270) * [Meteostick] Add windvane calibration * [Meteostick] Added daily rain accumulation Signed-off-by: Cor Hoogendoorn --- .../org.openhab.binding.meteostick/README.md | 19 ++-- .../internal/MeteostickBindingConstants.java | 2 + .../internal/MeteostickHandlerFactory.java | 8 +- .../handler/MeteostickSensorHandler.java | 96 +++++++++++++------ .../OH-INF/i18n/meteostick.properties | 4 + .../resources/OH-INF/thing/thing-types.xml | 17 ++++ 6 files changed, 106 insertions(+), 40 deletions(-) diff --git a/bundles/org.openhab.binding.meteostick/README.md b/bundles/org.openhab.binding.meteostick/README.md index d572237f3a309..3e838e38a39a5 100644 --- a/bundles/org.openhab.binding.meteostick/README.md +++ b/bundles/org.openhab.binding.meteostick/README.md @@ -41,10 +41,11 @@ Set mode to one of the following depending on your device and region: ### meteostick_davis_iss Configuration Options -| Option | Description | -|---------|-------------------------------------------| -| channel | Sets the RF channel used for this sensor | -| spoon | Size of rain spoon assembly for this sensor in mm. Default value is 0.254 (0.01") for use with Davis part number 7345.280. Set to 0.2 for use with Davis part number 7345.319 | +| Option | Description | +|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| channel | Sets the RF channel used for this sensor | +| spoon | Size of rain spoon assembly for this sensor in mm. Default value is 0.254 (0.01") for use with Davis part number 7345.280. Set to 0.2 for use with Davis part number 7345.319 | +| deltaWindDirection | For Davis 6410, 7911 & 7914 anemometers, if your anemometer cannot be mounted aiming true North set the direction it is aiming here (0 to 359 degrees). Default is 0 (for North) | ## Channels @@ -69,15 +70,17 @@ Set mode to one of the following depending on your device and region: | rain-raw | Number | Raw rain counter from the tipping spoon sensor | | rain-currenthour | Number:Length | The rainfall in the last 60 minutes | | rain-lasthour | Number:Length | The rainfall in the previous hour | +| rain-today | Number:Length | Accumulated rainfall for today | solar-power | Number | Solar power from the sensor station | | signal-strength | Number | Received signal strength | | low-battery | Switch | Low battery warning | #### Rainfall -There are three channels associated with rainfall. +There are four channels associated with rainfall. The raw counter from the tipping bucket is provided, the rainfall in the last 60 minutes is updated on each received rainfall and provides the past 60 minutes of rainfall. The rainfall in the previous hour is the rainfall for each hour of the day and is updated on the hour. +The accumulated rainfall for today provides the amount of rain for the current date and will reset to 0 at timezone's midnight. ## Full Example @@ -95,10 +98,10 @@ Things can be defined in the .things file as follows: ```java meteostick:meteostick_bridge:receiver [ port="/dev/tty.usbserial-AI02XA60", mode=1 ] -meteostick:meteostick_davis_iss:iss (meteostick:meteostick_bridge:receiver) [ channel=1, spoon=0.2 ] +meteostick:meteostick_davis_iss:iss (meteostick:meteostick_bridge:receiver) [ channel=1, spoon=0.2, deltaWindDirection=0 ] ``` -Note the configuration options for `port`, `mode`, `channel` and `spoon` above and adjust as needed for your specific hardware. +Note the configuration options for `port`, `mode`, `channel`, `deltaWindDirection` and `spoon` above and adjust as needed for your specific hardware. ### items/meteostick.items @@ -112,6 +115,7 @@ Number:Speed DavisVantageVueWindSpeed "ISS Wind Speed [%.1f m/s]" { channel="met Number:Speed DavisVantageVueWindSpeedAverage "ISS Average Wind Speed [%.1f m/s]" { channel="meteostick:meteostick_davis_iss:iss:wind-speed-last2min-average" } Number:Speed DavisVantageVueWindSpeedMaximum "ISS Maximum Wind Speed [%.1f m/s]" { channel="meteostick:meteostick_davis_iss:iss:wind-speed-last2min-maximum" } Number:Length DavisVantageVueRainCurrentHour "ISS Rain Current Hour [%.1f mm]" { channel="meteostick:meteostick_davis_iss:iss:rain-currenthour" } +Number:Length DavisVantageVueRainToday "ISS Rain Today [%.1f mm]" { channel="meteostick:meteostick_davis_iss:iss:rain-today" } ``` ### rules/meteostick.rules @@ -157,6 +161,7 @@ then 'dewptf' -> dewptf, 'tempf' -> DavisVantageVueOutdoorTemperature.getStateAs(QuantityType).toUnit('°F').doubleValue, 'rainin' -> DavisVantageVueRainCurrentHour.getStateAs(QuantityType).toUnit('in').doubleValue, + 'dailyrainin' -> DavisVantageVueRainToday.getStateAs(QuantityType).toUnit('in').doubleValue, 'baromin' -> MeteoStickPressure.getStateAs(QuantityType).toUnit('inHg').doubleValue, 'softwaretype' -> 'openHAB 2.4') diff --git a/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickBindingConstants.java b/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickBindingConstants.java index 782e5090f1037..6e0f3360945d6 100644 --- a/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickBindingConstants.java +++ b/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickBindingConstants.java @@ -38,6 +38,7 @@ public class MeteostickBindingConstants { public static final String CHANNEL_RAIN_RAW = "rain-raw"; public static final String CHANNEL_RAIN_CURRENTHOUR = "rain-currenthour"; public static final String CHANNEL_RAIN_LASTHOUR = "rain-lasthour"; + public static final String CHANNEL_RAIN_TODAY = "rain-today"; public static final String CHANNEL_WIND_SPEED = "wind-speed"; public static final String CHANNEL_WIND_DIRECTION = "wind-direction"; public static final String CHANNEL_WIND_SPEED_LAST2MIN_AVERAGE = "wind-speed-last2min-average"; @@ -51,6 +52,7 @@ public class MeteostickBindingConstants { public static final String PARAMETER_CHANNEL = "channel"; public static final String PARAMETER_SPOON = "spoon"; public static final String PARAMETER_SPOON_DEFAULT = "0.254"; + public static final String PARAMETER_WINDVANE = "deltaWindDirection"; // Miscellaneous constants public static final long HOUR_IN_SEC = 60 * 60; diff --git a/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickHandlerFactory.java b/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickHandlerFactory.java index 77731ecfe4433..a7dcae48b1f08 100644 --- a/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickHandlerFactory.java +++ b/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/MeteostickHandlerFactory.java @@ -17,6 +17,7 @@ import org.openhab.binding.meteostick.internal.handler.MeteostickBridgeHandler; import org.openhab.binding.meteostick.internal.handler.MeteostickSensorHandler; import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.scheduler.CronScheduler; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -41,10 +42,13 @@ public class MeteostickHandlerFactory extends BaseThingHandlerFactory { private Logger logger = LoggerFactory.getLogger(MeteostickHandlerFactory.class); private final SerialPortManager serialPortManager; + private final CronScheduler scheduler; @Activate - public MeteostickHandlerFactory(final @Reference SerialPortManager serialPortManager) { + public MeteostickHandlerFactory(final @Reference SerialPortManager serialPortManager, + final @Reference CronScheduler scheduler) { this.serialPortManager = serialPortManager; + this.scheduler = scheduler; } @Override @@ -64,7 +68,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } if (MeteostickSensorHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) { - return new MeteostickSensorHandler(thing); + return new MeteostickSensorHandler(thing, scheduler); } return null; diff --git a/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/handler/MeteostickSensorHandler.java b/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/handler/MeteostickSensorHandler.java index cf4d9a49e8b98..b7131a7a2f13d 100644 --- a/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/handler/MeteostickSensorHandler.java +++ b/bundles/org.openhab.binding.meteostick/src/main/java/org/openhab/binding/meteostick/internal/handler/MeteostickSensorHandler.java @@ -31,6 +31,8 @@ import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.scheduler.CronScheduler; +import org.openhab.core.scheduler.ScheduledCompletableFuture; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -48,25 +50,33 @@ * * @author Chris Jackson - Initial contribution * @author John Cocula - Added variable spoon size, UoM, wind stats, bug fixes + * @author Cor Hoogendoorn - Added option for wind vanes not facing North and cumulative rainfall for today */ public class MeteostickSensorHandler extends BaseThingHandler implements MeteostickEventListener { + private static final String DAILY_MIDNIGHT = "1 0 0 * * ? *"; + private final CronScheduler cronScheduler; public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DAVIS); private final Logger logger = LoggerFactory.getLogger(MeteostickSensorHandler.class); private int channel = 0; + private int deltawinddir = 0; + private int rainspoonold = -1; + private BigDecimal rainfallToday = BigDecimal.ZERO; private BigDecimal spoon = new BigDecimal(PARAMETER_SPOON_DEFAULT); private MeteostickBridgeHandler bridgeHandler; private RainHistory rainHistory = new RainHistory(HOUR_IN_MSEC); private WindHistory windHistory = new WindHistory(2 * 60 * 1000); // 2 minutes private ScheduledFuture rainHourlyJob; + private ScheduledCompletableFuture rainMidnightJob; private ScheduledFuture wind2MinJob; private ScheduledFuture offlineTimerJob; private Date lastData; - public MeteostickSensorHandler(Thing thing) { + public MeteostickSensorHandler(Thing thing, final CronScheduler scheduler) { super(thing); + this.cronScheduler = scheduler; } @Override @@ -79,10 +89,14 @@ public void initialize() { if (spoon == null) { spoon = new BigDecimal(PARAMETER_SPOON_DEFAULT); } - logger.debug("Initializing MeteoStick handler - Channel {}, Spoon size {} mm.", channel, spoon); + + deltawinddir = ((BigDecimal) getConfig().get(PARAMETER_WINDVANE)).intValue(); + + logger.debug("Initializing MeteoStick handler - Channel {}, Spoon size {} mm, Wind vane offset {} °", channel, + spoon, deltawinddir); Runnable rainRunnable = () -> { - BigDecimal rainfall = rainHistory.getTotal(spoon); + BigDecimal rainfall = rainHistory.getTotal(); rainfall.setScale(1, RoundingMode.DOWN); updateState(new ChannelUID(getThing().getUID(), CHANNEL_RAIN_LASTHOUR), new QuantityType<>(rainfall, MILLI(METRE))); @@ -92,6 +106,9 @@ public void initialize() { long start = HOUR_IN_SEC - ((System.currentTimeMillis() % HOUR_IN_MSEC) / 1000); rainHourlyJob = scheduler.scheduleWithFixedDelay(rainRunnable, start, HOUR_IN_SEC, TimeUnit.SECONDS); + // Scheduling a job at midnight to reset today's rainfall to 0 + rainMidnightJob = cronScheduler.schedule(this::dailyJob, DAILY_MIDNIGHT); + Runnable windRunnable = () -> { WindStats stats = windHistory.getStats(); updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_SPEED_LAST2MIN_AVERAGE), @@ -114,6 +131,10 @@ public void dispose() { rainHourlyJob.cancel(true); } + if (rainMidnightJob != null) { + rainMidnightJob.cancel(true); + } + if (wind2MinJob != null) { wind2MinJob.cancel(true); } @@ -193,18 +214,43 @@ public void onDataReceived(String[] data) { processSignalStrength(data[3]); processBattery(data.length == 5); - rainHistory.put(rain); + rain &= 0x7F; + int totalspoon = 0; + if (rainspoonold < 0) { + rainspoonold = rain; + } + if (rain < rainspoonold) { + totalspoon = 128 - rainspoonold + rain; + } else { + totalspoon = rain - rainspoonold; + } + + BigDecimal rainincrease = BigDecimal.valueOf(totalspoon).multiply(spoon); + rainHistory.put(rainincrease); - BigDecimal rainfall = rainHistory.getTotal(spoon); + BigDecimal rainfall = rainHistory.getTotal(); rainfall.setScale(1, RoundingMode.DOWN); updateState(new ChannelUID(getThing().getUID(), CHANNEL_RAIN_CURRENTHOUR), new QuantityType<>(rainfall, MILLI(METRE))); + + rainfallToday = rainfallToday.add(rainincrease); + rainfallToday.setScale(1, RoundingMode.DOWN); + updateState(new ChannelUID(getThing().getUID(), CHANNEL_RAIN_TODAY), + new QuantityType<>(rainfallToday, MILLI(METRE))); + rainspoonold = rain; break; case "W": // Wind BigDecimal windSpeed = new BigDecimal(data[2]); int windDirection = Integer.parseInt(data[3]); updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_SPEED), new QuantityType<>(windSpeed, METRE_PER_SECOND)); + if (deltawinddir != 0) { + if (windDirection < (360 - deltawinddir)) { + windDirection += deltawinddir; + } else { + windDirection -= (360 - deltawinddir); + } + } updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_DIRECTION), new QuantityType<>(windDirection, DEGREE_ANGLE)); @@ -265,43 +311,24 @@ public void removeOldEntries() { } } - class RainHistory extends SlidingTimeWindow { + class RainHistory extends SlidingTimeWindow { public RainHistory(long period) { super(period); } - public BigDecimal getTotal(BigDecimal spoon) { + public BigDecimal getTotal() { removeOldEntries(); - - int least = -1; - int total = 0; - + BigDecimal raintotalmap = BigDecimal.ZERO; synchronized (storage) { - for (int value : storage.values()) { - - /* - * Rain counters have been seen to wrap at 127 and also at 255. - * The Meteostick documentation only mentions 255 at the time of - * this writing. This potential difference is solved by having - * all rain counters wrap at 127 (0x7F) by removing the high bit. - */ - value &= 0x7F; - - if (least == -1) { - least = value; - continue; - } + for (BigDecimal value : storage.values()) { + + raintotalmap = raintotalmap.add(value); - if (value < least) { - total = 128 - least + value; - } else { - total = value - least; - } } } - return BigDecimal.valueOf(total).multiply(spoon); + return raintotalmap; } } @@ -392,4 +419,11 @@ private synchronized void startTimeoutCheck() { // Scheduling a job on each hour to update the last hour rainfall offlineTimerJob = scheduler.schedule(pollingRunnable, 90, TimeUnit.SECONDS); } + + private void dailyJob() { + // Daily job to reset the daily rain accumulation + rainfallToday = BigDecimal.ZERO; + updateState(new ChannelUID(getThing().getUID(), CHANNEL_RAIN_TODAY), + new QuantityType<>(rainfallToday, MILLI(METRE))); + } } diff --git a/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/i18n/meteostick.properties b/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/i18n/meteostick.properties index 90164403a072e..315e4ec7a5121 100644 --- a/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/i18n/meteostick.properties +++ b/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/i18n/meteostick.properties @@ -32,6 +32,8 @@ thing-type.config.meteostick.meteostick_davis_iss.channel.option.5 = Channel 5 thing-type.config.meteostick.meteostick_davis_iss.channel.option.6 = Channel 6 thing-type.config.meteostick.meteostick_davis_iss.channel.option.7 = Channel 7 thing-type.config.meteostick.meteostick_davis_iss.channel.option.8 = Channel 8 +thing-type.config.meteostick.meteostick_davis_iss.deltaWindDirection.label = Wind Vane Direction +thing-type.config.meteostick.meteostick_davis_iss.deltaWindDirection.description = Specifies the direction that the wind vane's North is actually pointed towards (0 to 359) thing-type.config.meteostick.meteostick_davis_iss.spoon.label = Spoon thing-type.config.meteostick.meteostick_davis_iss.spoon.description = Specifies the amount of rain needed to tip spoon @@ -51,6 +53,8 @@ channel-type.meteostick.rain-lasthour.label = Rainfall (previous Hour) channel-type.meteostick.rain-lasthour.description = Rainfall in the previous hour channel-type.meteostick.rain-raw.label = Rainfall (Raw) channel-type.meteostick.rain-raw.description = A counter between 0 and 255 in spoon-sized steps +channel-type.meteostick.rain-today.label = Rainfall Today +channel-type.meteostick.rain-today.description = Total rainfall today channel-type.meteostick.solar-power.label = Solar Power channel-type.meteostick.solar-power.description = Solar panel power percentage channel-type.meteostick.wind-direction-last2min-average.label = Wind Direction (Average) diff --git a/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/thing/thing-types.xml index 561d09957fbf0..dc526bb0bce13 100644 --- a/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.meteostick/src/main/resources/OH-INF/thing/thing-types.xml @@ -52,6 +52,7 @@ + @@ -79,6 +80,13 @@ 0.254 mm + + + + Specifies the direction that the wind vane's North is actually pointed towards (0 to 359) + 0 + ° + @@ -190,6 +198,15 @@ + + Number:Length + + Total rainfall today + Rain + + + + Number From 0745cdbfbb723eed0097dab51ecb7d9af09c45f8 Mon Sep 17 00:00:00 2001 From: Roland Tapken Date: Sun, 11 Feb 2024 15:13:52 +0100 Subject: [PATCH 11/13] [dolbycp] Initial Contribution (#16216) * Added new binding dolbycp Signed-off-by: Roland Tapken --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.dolbycp/NOTICE | 25 ++ bundles/org.openhab.binding.dolbycp/README.md | 91 ++++++ bundles/org.openhab.binding.dolbycp/pom.xml | 26 ++ .../src/main/feature/feature.xml | 9 + .../internal/DolbyCPBindingConstants.java | 46 +++ .../internal/DolbyCPConfiguration.java | 43 +++ .../dolbycp/internal/DolbyCPHandler.java | 298 ++++++++++++++++++ .../internal/DolbyCPHandlerFactory.java | 55 ++++ .../src/main/resources/OH-INF/addon/addon.xml | 11 + .../resources/OH-INF/i18n/dolbycp.properties | 51 +++ .../resources/OH-INF/thing/thing-types.xml | 117 +++++++ bundles/pom.xml | 1 + 14 files changed, 779 insertions(+) create mode 100644 bundles/org.openhab.binding.dolbycp/NOTICE create mode 100644 bundles/org.openhab.binding.dolbycp/README.md create mode 100644 bundles/org.openhab.binding.dolbycp/pom.xml create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPBindingConstants.java create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPConfiguration.java create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandler.java create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandlerFactory.java create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/i18n/dolbycp.properties create mode 100644 bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 8c912b54d30c6..2cd8f3d172843 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -77,6 +77,7 @@ /bundles/org.openhab.binding.digitalstrom/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.dlinksmarthome/ @MikeJMajor /bundles/org.openhab.binding.dmx/ @openhab/add-ons-maintainers +/bundles/org.openhab.binding.dolbycp/ @Cybso /bundles/org.openhab.binding.dominoswiss/ @Friesoch /bundles/org.openhab.binding.doorbird/ @mhilbush /bundles/org.openhab.binding.draytonwiser/ @andrew-schofield diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 2c85b5dcb19e3..e0a5201f678fd 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -376,6 +376,11 @@ org.openhab.binding.dmx ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.dolbycp + ${project.version} + org.openhab.addons.bundles org.openhab.binding.dominoswiss diff --git a/bundles/org.openhab.binding.dolbycp/NOTICE b/bundles/org.openhab.binding.dolbycp/NOTICE new file mode 100644 index 0000000000000..6f57bb4acb442 --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/NOTICE @@ -0,0 +1,25 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons + +== Third Party Dependencies +Third Party Dependencies are used inside the code of this project, but not +redistributed. Instead, Openhab retrieves the dependencies directly from +public sources. + +=== de.cybso.cp750:cp750 +Author: Roland Tapken +URL: https://github.com/cybso/cp750-java +License: Apache License Version 2.0 + + diff --git a/bundles/org.openhab.binding.dolbycp/README.md b/bundles/org.openhab.binding.dolbycp/README.md new file mode 100644 index 0000000000000..9b4c4eaa60891 --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/README.md @@ -0,0 +1,91 @@ +# DolbyCP Binding + +This binding is intended to connect to a _Dolby Digital Cinema Processor CP750_. +Support for CP950 may be added in future (if someone who owns one helps to test it). + +It uses ASCII commands send over a TCP connection on port 61408. +Please note that the CP750 only accepts up to 20 simultaneous connection and will discard the oldest connection if a 21st connection is established. +So be sure to grateful shutdown each connection using the client's close() method, or it's AutoCloseable functionality. + +This binding wraps the CP750 Java library from https://github.com/Cybso/cp750-java/. + +This project is NOT affiliated with, funded, or in any way associated with Dolby Laboratories, Inc. + +## Supported Things + +- `cp750` - The Dolby Digital Cinema Processor CP750 Device. + +## Thing Configuration + +Normally, only the hostname or IP address must be configured. + +### `cp750` Thing Configuration + +| Name | Type | Description | Default | Required | Advanced | +|-------------------|---------|--------------------------------------------------------------|---------|----------|----------| +| hostname | text | Hostname or IP address of the device | N/A | yes | no | +| port | integer | TCP port if different from 61408 | 61408 | no | no | +| refreshInterval | integer | Interval the device is polled in seconds | 5 | no | no | +| reconnectInterval | integer | Interval a new connection is tried after IO error in seconds | 10 | no | no | + +## Properties + +| Name | Description | +|-------------------|----------------------------------------------------------| +| osVersion | The operating system's version as returned by the device | + +## Channels + +These channels can be used to retrieve the current device state and change the controls. + +The input mode can be either be controlled by the "input" string channel, or by writing an ON value to the dedicated switch channels, which represents the physical buttons on the CP750 device. + +| Channel | Type | Read/Write | Description | +|------------|--------|------------|-----------------------------------------------------------------------------------------------------------------| +| fader | Dimmer | RW | Fader value (0 to 100) | +| mute | Switch | RW | Mute (ON or OFF) | +| input | String | RW | Input channel as string (one of "analog", "dig_1", "dig_2", "dig_3", "dig_4", "non-sync" or "mic") | +| analog | Switch | RW | Is ON if input mode is 'analog'. When an ON command is retrieved, input mode will be changed to this channel. | +| dig1 | Switch | RW | Is ON if input mode is 'dig_1'. When an ON command is retrieved, input mode will be changed to this channel. | +| dig2 | Switch | RW | Is ON if input mode is 'dig_2'. When an ON command is retrieved, input mode will be changed to this channel. | +| dig3 | Switch | RW | Is ON if input mode is 'dig_3'. When an ON command is retrieved, input mode will be changed to this channel. | +| dig4 | Switch | RW | Is ON if input mode is 'dig_4'. When an ON command is retrieved, input mode will be changed to this channel. | +| nonsync | Switch | RW | Is ON if input mode is 'non-sync'. When an ON command is retrieved, input mode will be changed to this channel. | +| mic | Switch | RW | Is ON if input mode is 'mic'. When an ON command is retrieved, input mode will be changed to this channel. | + +## Full Example + +The following example is for a device connected at IP 192.168.1.135 on port 61408 with all channels linked to items. + +demo.things: + +```java +Thing dolbycp:cp750:myCp750 "CP750" @ "Projector Room" [hostname="192.168.1.135", port=61408, refreshInterval=5, reconnectInterval=10] { + Channels: + Type fader : myFader "Fader control" + Type mute : myMute "Mute control" + Type input : myInput "Input mode control" + Type analog : myAnalogBtn "Input Mode 'analog' switch control" + Type dig1 : myDig1Btn "Input Mode 'Digital 1' switch control" + Type dig2 : myDig2Btn "Input Mode 'Digital 2' switch control" + Type dig3 : myDig3Btn "Input Mode 'Digital 3' switch control" + Type dig4 : myDig4Btn "Input Mode 'Digital 4' switch control" + Type nonsync : myNonSyncBtn "Input Mode 'Non-Sync' switch control" + Type mic : myMicBtn "Input Mode 'Microphone' switch control" +} +``` + +demo.items: + +```java +Dimmer mycp750_volume "Volume [%d]" { channel="dolbycp:cp750:myCp750:myFader" } +Switch mycp750_mute "Mute" { channel="dolbycp:cp750:myCp750:myMute" } +String mycp750_input "Input Mode [%s]" { channel="dolbycp:cp750:myCp750:myInput" } +Switch mycp750_analog "Input Mode Analog" { channel="dolbycp:cp750:myCp750:myAnalogBtn" } +Switch mycp750_dig1 "Input Mode Digital 1" { channel="dolbycp:cp750:myCp750:myDig1Btn" } +Switch mycp750_dig2 "Input Mode Digital 2" { channel="dolbycp:cp750:myCp750:myDig2Btn" } +Switch mycp750_dig3 "Input Mode Digital 3" { channel="dolbycp:cp750:myCp750:myDig3Btn" } +Switch mycp750_dig4 "Input Mode Digital 4" { channel="dolbycp:cp750:myCp750:myDig4Btn" } +Switch mycp750_nonsyc "Input Mode Non-Sync" { channel="dolbycp:cp750:myCp750:myNonSyncBtn" } +Switch mycp750_mic "Input Mode Microphone" { channel="dolbycp:cp750:myCp750:myMicBtn" } +``` diff --git a/bundles/org.openhab.binding.dolbycp/pom.xml b/bundles/org.openhab.binding.dolbycp/pom.xml new file mode 100644 index 0000000000000..4df518dc86e74 --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.2.0-SNAPSHOT + + + org.openhab.binding.dolbycp + + openHAB Add-ons :: Bundles :: Dolby Cinema Processor Binding + + + + de.cybso.cp750 + cp750 + 0.2 + compile + + + + diff --git a/bundles/org.openhab.binding.dolbycp/src/main/feature/feature.xml b/bundles/org.openhab.binding.dolbycp/src/main/feature/feature.xml new file mode 100644 index 0000000000000..ce9b50e997c7d --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.dolbycp/${project.version} + + diff --git a/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPBindingConstants.java b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPBindingConstants.java new file mode 100644 index 0000000000000..fea172241ab52 --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPBindingConstants.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dolbycp.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link DolbyCPBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Roland Tapken - Initial contribution + */ +@NonNullByDefault +public class DolbyCPBindingConstants { + + private static final String BINDING_ID = "dolbycp"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "cp750"); + + // List of all Channel ids + public static final String CHANNEL_FADER = "fader"; + public static final String CHANNEL_MUTE = "mute"; + public static final String CHANNEL_INPUT = "input"; + public static final String CHANNEL_ANALOG = "analog"; + public static final String CHANNEL_DIG1 = "dig1"; + public static final String CHANNEL_DIG2 = "dig2"; + public static final String CHANNEL_DIG3 = "dig3"; + public static final String CHANNEL_DIG4 = "dig4"; + public static final String CHANNEL_NONSYNC = "nonsync"; + public static final String CHANNEL_MIC = "mic"; + + // List of properties + public static final String PROPERTY_VERSION = "osVersion"; +} diff --git a/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPConfiguration.java b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPConfiguration.java new file mode 100644 index 0000000000000..3a11450c96c9e --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPConfiguration.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dolbycp.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link DolbyCPConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Roland Tapken - Initial contribution + */ +@NonNullByDefault +public class DolbyCPConfiguration { + /** + * Hostname or IP address of the CP750 device + */ + public String hostname = ""; + + /** + * TCP Port to connect to (default: 61408) + */ + public int port = 61408; + + /** + * Interval in seconds to update channels + */ + public int refreshInterval = 5; + + /** + * Reconnect interval in seconds after a broken TCP connection + */ + public int reconnectInterval = 10; +} diff --git a/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandler.java b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandler.java new file mode 100644 index 0000000000000..7ba197af751d9 --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandler.java @@ -0,0 +1,298 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dolbycp.internal; + +import static org.openhab.binding.dolbycp.internal.DolbyCPBindingConstants.*; + +import java.io.IOException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.IncreaseDecreaseType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.cybso.cp750.CP750Client; +import de.cybso.cp750.CP750Field; +import de.cybso.cp750.CP750InputMode; +import de.cybso.cp750.CP750Listener; + +/** + * The {@link DolbyCPHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Roland Tapken - Initial contribution + */ +@NonNullByDefault +public class DolbyCPHandler extends BaseThingHandler implements CP750Listener { + + private final Logger logger = LoggerFactory.getLogger(DolbyCPHandler.class); + + private @Nullable DolbyCPConfiguration config; + + private @Nullable CP750Client client; + + private @Nullable ScheduledFuture scheduleFuture; + + private @Nullable CP750InputMode currentInputMode; + + public DolbyCPHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + CP750Client client = this.client; + if (client != null) { + try { + if (command instanceof RefreshType) { + client.refresh(); + return; + } + + switch (channelUID.getId()) { + case CHANNEL_INPUT -> { + if (command instanceof StringType commandAsStringType) { + CP750InputMode mode = CP750InputMode.byValue(commandAsStringType.toString()); + if (mode != null) { + client.setInputMode(mode); + } + } + } + case CHANNEL_ANALOG -> { + if (command == OnOffType.ON) { + client.setInputMode(CP750InputMode.ANALOG); + } else if (currentInputMode == CP750InputMode.ANALOG) { + client.setInputMode(CP750InputMode.LAST); + } + } + case CHANNEL_DIG1 -> { + if (command == OnOffType.ON) { + client.setInputMode(CP750InputMode.DIG_1); + } else if (currentInputMode == CP750InputMode.DIG_1) { + client.setInputMode(CP750InputMode.LAST); + } + } + case CHANNEL_DIG2 -> { + if (command == OnOffType.ON) { + client.setInputMode(CP750InputMode.DIG_2); + } else if (currentInputMode == CP750InputMode.DIG_2) { + client.setInputMode(CP750InputMode.LAST); + } + } + case CHANNEL_DIG3 -> { + if (command == OnOffType.ON) { + client.setInputMode(CP750InputMode.DIG_3); + } else if (currentInputMode == CP750InputMode.DIG_3) { + client.setInputMode(CP750InputMode.LAST); + } + } + case CHANNEL_DIG4 -> { + if (command == OnOffType.ON) { + client.setInputMode(CP750InputMode.DIG_4); + } else if (currentInputMode == CP750InputMode.DIG_4) { + client.setInputMode(CP750InputMode.LAST); + } + } + case CHANNEL_MIC -> { + if (command == OnOffType.ON) { + client.setInputMode(CP750InputMode.MIC); + } else if (currentInputMode == CP750InputMode.MIC) { + client.setInputMode(CP750InputMode.LAST); + } + } + case CHANNEL_NONSYNC -> { + if (command == OnOffType.ON) { + client.setInputMode(CP750InputMode.NON_SYNC); + } else if (currentInputMode == CP750InputMode.NON_SYNC) { + client.setInputMode(CP750InputMode.LAST); + } + } + case CHANNEL_MUTE -> { + if (command instanceof OnOffType) { + client.setMuted(command == OnOffType.ON); + } + } + case CHANNEL_FADER -> { + if (command instanceof DecimalType commandAsDecimalType) { + client.setFader(commandAsDecimalType.intValue()); + } + if (command instanceof IncreaseDecreaseType) { + client.setFaderDelta(command == IncreaseDecreaseType.INCREASE ? 1 : -1); + } + } + } + } catch (IOException e) { + releaseAndReconnect(e.getMessage()); + } + } + } + + @Override + public void initialize() { + final DolbyCPConfiguration config = getConfigAs(DolbyCPConfiguration.class); + this.config = config; + updateStatus(ThingStatus.UNKNOWN); + + scheduler.execute(() -> { + try { + CP750Client client = new CP750Client(config.hostname, config.port); + this.client = client; + for (CP750Field field : CP750Field.values()) { + if (field == CP750Field.SYSINFO_VERSION) { + // This needs to be only updated once + client.addOnetimeListener(field, this); + } else { + client.addListener(field, this); + } + } + updateStatus(ThingStatus.ONLINE); + + // Schedule first refresh by now and more after configured refresh interval + this.scheduleFuture = scheduler.scheduleWithFixedDelay(this::refresh, 100, config.refreshInterval, + TimeUnit.SECONDS); + } catch (IOException e) { + releaseAndReconnect(e.getMessage()); + } + }); + } + + /** + * Cancel scheduled futures and close the client connection. + * Will not update status, this has to be done by the invoking method. + */ + private void releaseResources() { + ScheduledFuture scheduleFuture = this.scheduleFuture; + this.scheduleFuture = null; + if (scheduleFuture != null) { + scheduleFuture.cancel(true); + } + CP750Client client = this.client; + this.client = null; + if (client != null) { + client.removeListener(this); + try { + client.close(); + } catch (Exception ignore) { + } + } + } + + /** + * Release resources and, if greater 0, tries to reconnect + * after configured time in seconds. + */ + private void releaseAndReconnect(@Nullable String errorMessage) { + releaseResources(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage); + DolbyCPConfiguration config = this.config; + if (config != null && config.reconnectInterval > 0) { + logger.debug("DolbyCP at {}:{} try to reconnect in {} seconds", config.hostname, config.port, + config.reconnectInterval); + scheduler.schedule(() -> { + if (getThing().getStatus() == ThingStatus.OFFLINE) { + // Will call disposeAndReconnect() if something goes wrong + initialize(); + } + }, config.reconnectInterval, TimeUnit.SECONDS); + } + } + + @Override + public void dispose() { + releaseResources(); + updateStatus(ThingStatus.OFFLINE); + super.dispose(); + } + + public void refresh() { + try { + CP750Client client = this.client; + if (client != null) { + client.refresh(); + } + } catch (IOException e) { + releaseAndReconnect(e.getMessage()); + } + } + + /** + * Handles input data + */ + @Override + public void receive(@Nullable CP750Field field, @Nullable String value) { + DolbyCPConfiguration config = this.config; + String hostname = config == null ? "unknown" : config.hostname; + int port = config == null ? -1 : config.port; + + logger.debug("DolbyCP at {}:{} received {} with value {}", hostname, port, field, value); + if (field == null || value == null) { + return; + } + + switch (field) { + case SYSINFO_VERSION -> updateProperty(PROPERTY_VERSION, value); + case SYS_MUTE -> updateState(CHANNEL_MUTE, OnOffType.from(value)); + case SYS_FADER -> updateState(CHANNEL_FADER, PercentType.valueOf(value)); + case SYS_INPUT_MODE -> { + CP750InputMode mode = CP750InputMode.byValue(value); + if (mode != null) { + this.currentInputMode = mode; + updateState(CHANNEL_INPUT, StringType.valueOf(value)); + OnOffType analog = OnOffType.OFF; + OnOffType dig1 = OnOffType.OFF; + OnOffType dig2 = OnOffType.OFF; + OnOffType dig3 = OnOffType.OFF; + OnOffType dig4 = OnOffType.OFF; + OnOffType nonsync = OnOffType.OFF; + OnOffType mic = OnOffType.OFF; + switch (mode) { + case ANALOG -> analog = OnOffType.ON; + case DIG_1 -> dig1 = OnOffType.ON; + case DIG_2 -> dig2 = OnOffType.ON; + case DIG_3 -> dig3 = OnOffType.ON; + case DIG_4 -> dig4 = OnOffType.ON; + case MIC -> mic = OnOffType.ON; + case NON_SYNC -> nonsync = OnOffType.ON; + default -> { + // Ignore unknown value + } + } + updateState(CHANNEL_ANALOG, analog); + updateState(CHANNEL_DIG1, dig1); + updateState(CHANNEL_DIG2, dig2); + updateState(CHANNEL_DIG3, dig3); + updateState(CHANNEL_DIG4, dig4); + updateState(CHANNEL_MIC, mic); + updateState(CHANNEL_NONSYNC, nonsync); + } + } + default -> { + // Ignore unknown value + } + } + } +} diff --git a/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandlerFactory.java b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandlerFactory.java new file mode 100644 index 0000000000000..5cdfeebda32da --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/java/org/openhab/binding/dolbycp/internal/DolbyCPHandlerFactory.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.dolbycp.internal; + +import static org.openhab.binding.dolbycp.internal.DolbyCPBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link DolbyCPHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Roland Tapken - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.dolbycp", service = ThingHandlerFactory.class) +public class DolbyCPHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAMPLE); + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_SAMPLE.equals(thingTypeUID)) { + return new DolbyCPHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..c92cd31a1fa74 --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,11 @@ + + + + binding + DolbyCP Binding + This is the binding for controlling a Dolby CP750 + local + + diff --git a/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/i18n/dolbycp.properties b/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/i18n/dolbycp.properties new file mode 100644 index 0000000000000..e4787bf493c66 --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/i18n/dolbycp.properties @@ -0,0 +1,51 @@ +# add-on + +addon.dolbycp.name = DolbyCP Binding +addon.dolbycp.description = This is the binding for controlling a Dolby CP750 + +# thing types + +thing-type.dolbycp.cp750.label = CP750 Device +thing-type.dolbycp.cp750.description = Binding for Dolby Cinema Processor CP750 + +# thing types config + +thing-type.config.dolbycp.cp750.hostname.label = Hostname +thing-type.config.dolbycp.cp750.hostname.description = Hostname or IP address of the device +thing-type.config.dolbycp.cp750.port.label = Port +thing-type.config.dolbycp.cp750.port.description = Port to access the device +thing-type.config.dolbycp.cp750.reconnectInterval.label = Retry Interval +thing-type.config.dolbycp.cp750.reconnectInterval.description = Reconnect interval in seconds after connection failure (0 = disabled) +thing-type.config.dolbycp.cp750.refreshInterval.label = Refresh Interval +thing-type.config.dolbycp.cp750.refreshInterval.description = Interval the device is polled in sec. + +# channel types + +channel-type.dolbycp.fader.label = Fader +channel-type.dolbycp.fader.description = Fader state +channel-type.dolbycp.input-analog.label = Analog Input +channel-type.dolbycp.input-analog.description = Handles the state of INPUT_MODE=analog +channel-type.dolbycp.input-dig1.label = Digital Input 1 +channel-type.dolbycp.input-dig1.description = Handles the state of INPUT_MODE=dig_1 +channel-type.dolbycp.input-dig2.label = Digital Input 2 +channel-type.dolbycp.input-dig2.description = Handles the state of INPUT_MODE=dig_3 +channel-type.dolbycp.input-dig3.label = Digital Input 3 +channel-type.dolbycp.input-dig3.description = Handles the state of INPUT_MODE=dig_3 +channel-type.dolbycp.input-dig4.label = Digital Input 4 +channel-type.dolbycp.input-dig4.description = Handles the state of INPUT_MODE=dig_4 +channel-type.dolbycp.input-mic.label = Mic Input +channel-type.dolbycp.input-mic.description = Handles the state of INPUT_MODE=mic +channel-type.dolbycp.input-non_sync.label = Non-Sync Input +channel-type.dolbycp.input-non_sync.description = Handles the state of INPUT_MODE=non_sync +channel-type.dolbycp.input.label = Current Input +channel-type.dolbycp.input.description = Handles the state of INPUT_MODE +channel-type.dolbycp.input.state.option.analog = Analog +channel-type.dolbycp.input.state.option.dig_1 = Digital 1 +channel-type.dolbycp.input.state.option.dig_2 = Digital 2 +channel-type.dolbycp.input.state.option.dig_3 = Digital 3 +channel-type.dolbycp.input.state.option.dig_4 = Digital 4 +channel-type.dolbycp.input.state.option.non_sync = Non-Sync +channel-type.dolbycp.input.state.option.mic = Microphone +channel-type.dolbycp.input.state.option.last = Last active input +channel-type.dolbycp.mute.label = Mute +channel-type.dolbycp.mute.description = Mute state diff --git a/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..3e00e67dbe81a --- /dev/null +++ b/bundles/org.openhab.binding.dolbycp/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,117 @@ + + + + + + Binding for Dolby Cinema Processor CP750 + + + + + + + + + + + + + + + + + + + + + network-address + + Hostname or IP address of the device + + + + 61408 + Port to access the device + + + true + + Interval the device is polled in sec. + 5 + + + true + + Reconnect interval in seconds after connection failure (0 = disabled) + 10 + + + + + + + Dimmer + + Fader state + + + Switch + + Mute state + + + String + + Handles the state of INPUT_MODE + + + + + + + + + + + + + + + Switch + + Handles the state of INPUT_MODE=analog + + + Switch + + Handles the state of INPUT_MODE=dig_1 + + + Switch + + Handles the state of INPUT_MODE=dig_3 + + + Switch + + Handles the state of INPUT_MODE=dig_3 + + + Switch + + Handles the state of INPUT_MODE=dig_4 + + + Switch + + Handles the state of INPUT_MODE=non_sync + + + Switch + + Handles the state of INPUT_MODE=mic + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 07b13fcb284ae..d6ec5faad6a3b 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -109,6 +109,7 @@ org.openhab.binding.digitalstrom org.openhab.binding.dlinksmarthome org.openhab.binding.dmx + org.openhab.binding.dolbycp org.openhab.binding.dominoswiss org.openhab.binding.doorbird org.openhab.binding.draytonwiser From 4c4f29283ce6b0449f7638f40e9aef44d6af5075 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 11 Feb 2024 21:40:09 +0100 Subject: [PATCH 12/13] [knx] Add tests for DPT 5, DPT 6, DPT 8, DPT 13 (#16396) Signed-off-by: Holger Friedrich --- .../knx/internal/itests/Back2BackTest.java | 108 +++++++++++++++++- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java index 36a57a51697d8..3ec8bb91dc81a 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java @@ -332,22 +332,39 @@ void testDpt3() { @Test void testDpt5() { - // TODO add tests for more subtypes + helper("5.001", new byte[] { 0 }, new QuantityType<>("0 %")); + helper("5.001", new byte[] { (byte) 0xff }, new QuantityType<>("100 %")); + // fallback: PercentType helper("5.001", new byte[] { 0 }, new PercentType(0)); helper("5.001", new byte[] { (byte) 0x80 }, new PercentType(50)); helper("5.001", new byte[] { (byte) 0xff }, new PercentType(100)); + helper("5.003", new byte[] { 0 }, new QuantityType<>("0 °")); + helper("5.003", new byte[] { (byte) 0xff }, new QuantityType<>("360 °")); + helper("5.004", new byte[] { 0 }, new QuantityType<>("0 %")); + helper("5.004", new byte[] { (byte) 0x64 }, new QuantityType<>("100 %")); + helper("5.004", new byte[] { (byte) 0xff }, new QuantityType<>("255 %")); + // PercentType cannot encode values >100%, not supported for 5.004 + helper("5.005", new byte[] { 42 }, new DecimalType(42)); + helper("5.005", new byte[] { (byte) 0xff }, new DecimalType(255)); + helper("5.006", new byte[] { 0 }, new DecimalType(0)); + helper("5.006", new byte[] { 42 }, new DecimalType(42)); + helper("5.006", new byte[] { (byte) 0xfe }, new DecimalType(254)); + helper("5.010", new byte[] { 42 }, new DecimalType(42)); helper("5.010", new byte[] { (byte) 0xff }, new DecimalType(255)); } @Test void testDpt6() { + helper("6.001", new byte[] { 0 }, new QuantityType<>("0 %")); + helper("6.001", new byte[] { (byte) 0x7f }, new QuantityType<>("127 %")); + helper("6.001", new byte[] { (byte) 0xff }, new QuantityType<>("-1 %")); + // PercentType cannot encode values >100% or <0%, not supported for 6.001 + helper("6.010", new byte[] { 0 }, new DecimalType(0)); helper("6.010", new byte[] { (byte) 0x7f }, new DecimalType(127)); helper("6.010", new byte[] { (byte) 0xff }, new DecimalType(-1)); - // TODO 6.001 is mapped to PercentType, which can only cover 0-100%, not -128..127% - // helper("6.001", new byte[] { 0 }, new DecimalType(0)); } @Test @@ -359,9 +376,33 @@ void testDpt7() { @Test void testDpt8() { - // TODO add tests for more subtypes helper("8.001", new byte[] { (byte) 0x7f, (byte) 0xff }, new DecimalType(32767)); helper("8.001", new byte[] { (byte) 0x80, (byte) 0x00 }, new DecimalType(-32768)); + helper("8.002", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 ms")); + helper("8.002", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 ms")); + helper("8.002", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms")); + helper("8.003", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-327680 ms")); + helper("8.003", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("327670 ms")); + helper("8.003", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms")); + helper("8.004", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-3276800 ms")); + helper("8.004", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("3276700 ms")); + helper("8.004", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 ms")); + helper("8.005", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 s")); + helper("8.005", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 s")); + helper("8.005", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 s")); + helper("8.006", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 min")); + helper("8.006", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 min")); + helper("8.006", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 min")); + helper("8.007", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 h")); + helper("8.007", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 h")); + helper("8.007", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 h")); + + helper("8.011", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 °")); + helper("8.011", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 °")); + helper("8.011", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 °")); + helper("8.012", new byte[] { (byte) 0x80, (byte) 0x00 }, new QuantityType<>("-32768 m")); + helper("8.012", new byte[] { (byte) 0x7f, (byte) 0xff }, new QuantityType<>("32767 m")); + helper("8.012", new byte[] { (byte) 0x00, (byte) 0x00 }, new QuantityType<>("0 m")); } @Test @@ -423,7 +464,6 @@ void testDpt12() { @Test void testDpt13() { - // TODO add tests for more subtypes helper("13.001", new byte[] { 0, 0, 0, 0 }, new DecimalType(0)); helper("13.001", new byte[] { 0, 0, 0, 42 }, new DecimalType(42)); helper("13.001", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, @@ -431,6 +471,64 @@ void testDpt13() { // KNX representation typically uses two's complement helper("13.001", new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }, new DecimalType(-1)); helper("13.001", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, new DecimalType(-2147483648)); + helper("13.002", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 m³/h")); + helper("13.002", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 m³/h")); + helper("13.002", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 m³/h")); + + helper("13.010", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 Wh")); + helper("13.010", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 Wh")); + helper("13.010", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 Wh")); + helper("13.011", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 VAh")); + helper("13.011", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 VAh")); + helper("13.011", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 VAh")); + helper("13.012", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 varh")); + helper("13.012", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 varh")); + helper("13.012", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 varh")); + helper("13.013", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 kWh")); + helper("13.013", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 kWh")); + helper("13.013", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 kWh")); + helper("13.014", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 VAh")); + helper("13.014", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648000 VAh")); + helper("13.014", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647000 VAh")); + helper("13.015", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 kvarh")); + helper("13.015", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 kvarh")); + helper("13.015", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 kvarh")); + helper("13.016", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 MWh")); + helper("13.016", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 MWh")); + helper("13.016", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 MWh")); + + helper("13.100", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 s")); + helper("13.100", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 s")); + helper("13.100", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 s")); + + helper("13.1200", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 l")); + helper("13.1200", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 l")); + helper("13.1200", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 l")); + helper("13.1201", new byte[] { 0, 0, 0, 0 }, new QuantityType<>("0 m³")); + helper("13.1201", new byte[] { (byte) 0x80, (byte) 0x0, (byte) 0x0, (byte) 0x0 }, + new QuantityType<>("-2147483648 m³")); + helper("13.1201", new byte[] { (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff }, + new QuantityType<>("2147483647 m³")); } @Test From 473b0acda1d320589e962187dde5b3696dd09872 Mon Sep 17 00:00:00 2001 From: Alexander Falkenstern Date: Sun, 11 Feb 2024 22:59:29 +0100 Subject: [PATCH 13/13] Remove duplicated code. (#16393) Signed-off-by: Alexander Falkenstern --- .../internal/handler/ShellyBaseHandler.java | 21 ++++--------------- .../shelly/internal/util/ShellyUtils.java | 9 +++++--- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index 08e73cb78247a..e300f5d95cf62 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -415,7 +415,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { case CHANNEL_SENSOR_SLEEPTIME: logger.debug("{}: Set sensor sleep time to {}", thingName, command); - int value = (int) getNumber(command); + int value = getNumber(command).intValue(); value = value > 0 ? Math.max(SHELLY_MOTION_SLEEPTIME_OFFSET, value - SHELLY_MOTION_SLEEPTIME_OFFSET) : 0; api.setSleepTime(value); @@ -432,7 +432,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.debug("{}: Select profile {}", thingName, command); int id = -1; if (command instanceof Number) { - id = (int) getNumber(command); + id = getNumber(command).intValue(); } else { String cmd = command.toString(); if (isDigit(cmd.charAt(0))) { @@ -458,7 +458,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_CONTROL_SETTEMP: logger.debug("{}: Set temperature to {}", thingName, command); - api.setValveTemperature(0, (int) getNumber(command)); + api.setValveTemperature(0, getNumber(command).intValue()); break; case CHANNEL_CONTROL_POSITION: logger.debug("{}: Set position to {}", thingName, command); @@ -470,7 +470,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_CONTROL_BTIMER: logger.debug("{}: Set boost timer to {}", thingName, command); - api.setValveBoostTime(0, (int) getNumber(command)); + api.setValveBoostTime(0, getNumber(command).intValue()); break; case CHANNEL_SENSOR_MUTE: if (profile.isSmoke && ((OnOffType) command) == OnOffType.ON) { @@ -514,19 +514,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } - private double getNumber(Command command) { - if (command instanceof QuantityType quantityCommand) { - return quantityCommand.doubleValue(); - } - if (command instanceof DecimalType decimalCommand) { - return decimalCommand.doubleValue(); - } - if (command instanceof Number numberCommand) { - return numberCommand.doubleValue(); - } - throw new IllegalArgumentException("Invalid Number type for conversion: " + command); - } - /** * Update device status and channels */ diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java index 5a4a82676676b..db494f2c177ec 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java @@ -236,13 +236,16 @@ public static DecimalType getDecimal(@Nullable Long value) { } public static Double getNumber(Command command) throws IllegalArgumentException { + if (command instanceof QuantityType quantityCommand) { + return quantityCommand.doubleValue(); + } if (command instanceof DecimalType decimalCommand) { return decimalCommand.doubleValue(); } - if (command instanceof QuantityType quantityCommand) { - return quantityCommand.doubleValue(); + if (command instanceof Number numberCommand) { + return numberCommand.doubleValue(); } - throw new IllegalArgumentException("Unable to convert number"); + throw new IllegalArgumentException("Invalid Number type for conversion: " + command); } public static OnOffType getOnOff(@Nullable Boolean value) {