2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.samsungtv.internal.service;
15 import static org.openhab.binding.samsungtv.internal.SamsungTvBindingConstants.*;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
23 import java.util.concurrent.CopyOnWriteArraySet;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.samsungtv.internal.service.api.EventListener;
28 import org.openhab.binding.samsungtv.internal.service.api.SamsungTvService;
29 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
30 import org.openhab.core.io.transport.upnp.UpnpIOService;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.UnDefType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Element;
40 import org.w3c.dom.NodeList;
43 * The {@link MainTVServerService} is responsible for handling MainTVServer
46 * @author Pauli Anttila - Initial contribution
49 public class MainTVServerService implements UpnpIOParticipant, SamsungTvService {
51 public static final String SERVICE_NAME = "MainTVServer2";
52 private static final List<String> SUPPORTED_CHANNELS = Arrays.asList(CHANNEL_NAME, CHANNEL, SOURCE_NAME, SOURCE_ID,
53 PROGRAM_TITLE, BROWSER_URL, STOP_BROWSER);
55 private final Logger logger = LoggerFactory.getLogger(MainTVServerService.class);
57 private final UpnpIOService service;
59 private final String udn;
61 private Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
63 private Set<EventListener> listeners = new CopyOnWriteArraySet<>();
65 private boolean started;
67 public MainTVServerService(UpnpIOService upnpIOService, String udn) {
68 logger.debug("Creating a Samsung TV MainTVServer service");
69 this.service = upnpIOService;
74 public List<String> getSupportedChannelNames() {
75 return SUPPORTED_CHANNELS;
79 public void addEventListener(EventListener listener) {
80 listeners.add(listener);
84 public void removeEventListener(EventListener listener) {
85 listeners.remove(listener);
90 service.registerParticipant(this);
96 service.unregisterParticipant(this);
101 public void clearCache() {
106 public boolean isUpnp() {
111 public void handleCommand(String channel, Command command) {
112 logger.trace("Received channel: {}, command: {}", channel, command);
118 if (command == RefreshType.REFRESH) {
119 if (isRegistered()) {
122 updateResourceState("MainTVAgent2", "GetCurrentMainTVChannel", null);
126 updateResourceState("MainTVAgent2", "GetCurrentExternalSource", null);
130 updateResourceState("MainTVAgent2", "GetCurrentContentRecognition", null);
133 updateResourceState("MainTVAgent2", "GetCurrentBrowserURL", null);
144 setSourceName(command);
145 // Clear value on cache to force update
146 stateMap.put("CurrentExternalSource", "");
149 setBrowserUrl(command);
150 // Clear value on cache to force update
151 stateMap.put("BrowserURL", "");
154 stopBrowser(command);
157 logger.warn("Samsung TV doesn't support transmitting for channel '{}'", channel);
161 private boolean isRegistered() {
162 return service.isRegistered(this);
166 public String getUDN() {
171 public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
175 public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
176 if (variable == null) {
180 String oldValue = stateMap.get(variable);
181 if ((value == null && oldValue == null) || (value != null && value.equals(oldValue))) {
182 logger.trace("Value '{}' for {} hasn't changed, ignoring update", value, variable);
186 stateMap.put(variable, (value != null) ? value : "");
188 for (EventListener listener : listeners) {
192 listener.valueReceived(PROGRAM_TITLE, (value != null) ? new StringType(value) : UnDefType.UNDEF);
195 listener.valueReceived(CHANNEL_NAME, (value != null) ? new StringType(value) : UnDefType.UNDEF);
197 case "CurrentExternalSource":
198 listener.valueReceived(SOURCE_NAME, (value != null) ? new StringType(value) : UnDefType.UNDEF);
200 case "CurrentChannel":
201 String currentChannel = (value != null) ? parseCurrentChannel(value) : null;
202 listener.valueReceived(CHANNEL,
203 currentChannel != null ? new DecimalType(currentChannel) : UnDefType.UNDEF);
206 listener.valueReceived(SOURCE_ID, (value != null) ? new DecimalType(value) : UnDefType.UNDEF);
209 listener.valueReceived(BROWSER_URL, (value != null) ? new StringType(value) : UnDefType.UNDEF);
215 protected Map<String, String> updateResourceState(String serviceId, String actionId,
216 @Nullable Map<String, String> inputs) {
217 Map<String, String> result = service.invokeAction(this, serviceId, actionId, inputs);
219 for (String variable : result.keySet()) {
220 onValueReceived(variable, result.get(variable), serviceId);
226 private void setSourceName(Command command) {
227 Map<String, String> result = updateResourceState("MainTVAgent2", "GetSourceList", null);
229 String source = command.toString();
232 String resultResult = result.get("Result");
233 if ("OK".equals(resultResult)) {
234 String xml = result.get("SourceList");
236 id = parseSourceList(xml).get(source);
239 logger.warn("Source list query failed, result='{}'", resultResult);
242 if (source != null && id != null) {
243 result = updateResourceState("MainTVAgent2", "SetMainTVSource",
244 SamsungTvUtils.buildHashMap("Source", source, "ID", id, "UiID", "0"));
246 resultResult = result.get("Result");
247 if ("OK".equals(resultResult)) {
248 logger.debug("Command successfully executed");
250 logger.warn("Command execution failed, result='{}'", resultResult);
253 logger.warn("Source id for '{}' couldn't be found", command.toString());
257 private void setBrowserUrl(Command command) {
258 Map<String, String> result = updateResourceState("MainTVAgent2", "RunBrowser",
259 SamsungTvUtils.buildHashMap("BrowserURL", command.toString()));
261 String resultResult = result.get("Result");
262 if ("OK".equals(resultResult)) {
263 logger.debug("Command successfully executed");
265 logger.warn("Command execution failed, result='{}'", resultResult);
269 private void stopBrowser(Command command) {
270 Map<String, String> result = updateResourceState("MainTVAgent2", "StopBrowser", null);
272 String resultResult = result.get("Result");
273 if ("OK".equals(resultResult)) {
274 logger.debug("Command successfully executed");
276 logger.warn("Command execution failed, result='{}'", resultResult);
280 private @Nullable String parseCurrentChannel(@Nullable String xml) {
281 String majorCh = null;
284 Document dom = SamsungTvUtils.loadXMLFromString(xml);
287 NodeList nodeList = dom.getDocumentElement().getElementsByTagName("MajorCh");
289 if (nodeList != null) {
290 majorCh = nodeList.item(0).getFirstChild().getNodeValue();
298 private Map<String, String> parseSourceList(String xml) {
299 Map<String, String> list = new HashMap<>();
301 Document dom = SamsungTvUtils.loadXMLFromString(xml);
304 NodeList nodeList = dom.getDocumentElement().getElementsByTagName("Source");
306 if (nodeList != null) {
307 for (int i = 0; i < nodeList.getLength(); i++) {
309 String sourceType = null;
312 Element element = (Element) nodeList.item(i);
313 NodeList l = element.getElementsByTagName("SourceType");
314 if (l != null && l.getLength() > 0) {
315 sourceType = l.item(0).getFirstChild().getNodeValue();
317 l = element.getElementsByTagName("ID");
318 if (l != null && l.getLength() > 0) {
319 id = l.item(0).getFirstChild().getNodeValue();
322 if (sourceType != null && id != null) {
323 list.put(sourceType, id);
333 public void onStatusChanged(boolean status) {
334 logger.debug("onStatusChanged: status={}", status);