]> git.basschouten.com Git - openhab-addons.git/blob
1b989530e6970e24f760d633f9cbe194ff937621
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.samsungtv.internal.service;
14
15 import static org.openhab.binding.samsungtv.internal.SamsungTvBindingConstants.*;
16
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.CopyOnWriteArraySet;
24
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;
41
42 /**
43  * The {@link MainTVServerService} is responsible for handling MainTVServer
44  * commands.
45  *
46  * @author Pauli Anttila - Initial contribution
47  */
48 @NonNullByDefault
49 public class MainTVServerService implements UpnpIOParticipant, SamsungTvService {
50
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);
54
55     private final Logger logger = LoggerFactory.getLogger(MainTVServerService.class);
56
57     private final UpnpIOService service;
58
59     private final String udn;
60
61     private Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
62
63     private Set<EventListener> listeners = new CopyOnWriteArraySet<>();
64
65     private boolean started;
66
67     public MainTVServerService(UpnpIOService upnpIOService, String udn) {
68         logger.debug("Creating a Samsung TV MainTVServer service");
69         this.service = upnpIOService;
70         this.udn = udn;
71     }
72
73     @Override
74     public List<String> getSupportedChannelNames() {
75         return SUPPORTED_CHANNELS;
76     }
77
78     @Override
79     public void addEventListener(EventListener listener) {
80         listeners.add(listener);
81     }
82
83     @Override
84     public void removeEventListener(EventListener listener) {
85         listeners.remove(listener);
86     }
87
88     @Override
89     public void start() {
90         service.registerParticipant(this);
91         started = true;
92     }
93
94     @Override
95     public void stop() {
96         service.unregisterParticipant(this);
97         started = false;
98     }
99
100     @Override
101     public void clearCache() {
102         stateMap.clear();
103     }
104
105     @Override
106     public boolean isUpnp() {
107         return true;
108     }
109
110     @Override
111     public void handleCommand(String channel, Command command) {
112         logger.trace("Received channel: {}, command: {}", channel, command);
113
114         if (!started) {
115             return;
116         }
117
118         if (command == RefreshType.REFRESH) {
119             if (isRegistered()) {
120                 switch (channel) {
121                     case CHANNEL:
122                         updateResourceState("MainTVAgent2", "GetCurrentMainTVChannel", null);
123                         break;
124                     case SOURCE_NAME:
125                     case SOURCE_ID:
126                         updateResourceState("MainTVAgent2", "GetCurrentExternalSource", null);
127                         break;
128                     case PROGRAM_TITLE:
129                     case CHANNEL_NAME:
130                         updateResourceState("MainTVAgent2", "GetCurrentContentRecognition", null);
131                         break;
132                     case BROWSER_URL:
133                         updateResourceState("MainTVAgent2", "GetCurrentBrowserURL", null);
134                         break;
135                     default:
136                         break;
137                 }
138             }
139             return;
140         }
141
142         switch (channel) {
143             case SOURCE_NAME:
144                 setSourceName(command);
145                 // Clear value on cache to force update
146                 stateMap.put("CurrentExternalSource", "");
147                 break;
148             case BROWSER_URL:
149                 setBrowserUrl(command);
150                 // Clear value on cache to force update
151                 stateMap.put("BrowserURL", "");
152                 break;
153             case STOP_BROWSER:
154                 stopBrowser(command);
155                 break;
156             default:
157                 logger.warn("Samsung TV doesn't support transmitting for channel '{}'", channel);
158         }
159     }
160
161     private boolean isRegistered() {
162         return service.isRegistered(this);
163     }
164
165     @Override
166     public String getUDN() {
167         return udn;
168     }
169
170     @Override
171     public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
172     }
173
174     @Override
175     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
176         if (variable == null) {
177             return;
178         }
179
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);
183             return;
184         }
185
186         stateMap.put(variable, (value != null) ? value : "");
187
188         for (EventListener listener : listeners) {
189
190             switch (variable) {
191                 case "ProgramTitle":
192                     listener.valueReceived(PROGRAM_TITLE, (value != null) ? new StringType(value) : UnDefType.UNDEF);
193                     break;
194                 case "ChannelName":
195                     listener.valueReceived(CHANNEL_NAME, (value != null) ? new StringType(value) : UnDefType.UNDEF);
196                     break;
197                 case "CurrentExternalSource":
198                     listener.valueReceived(SOURCE_NAME, (value != null) ? new StringType(value) : UnDefType.UNDEF);
199                     break;
200                 case "CurrentChannel":
201                     String currentChannel = (value != null) ? parseCurrentChannel(value) : null;
202                     listener.valueReceived(CHANNEL,
203                             currentChannel != null ? new DecimalType(currentChannel) : UnDefType.UNDEF);
204                     break;
205                 case "ID":
206                     listener.valueReceived(SOURCE_ID, (value != null) ? new DecimalType(value) : UnDefType.UNDEF);
207                     break;
208                 case "BrowserURL":
209                     listener.valueReceived(BROWSER_URL, (value != null) ? new StringType(value) : UnDefType.UNDEF);
210                     break;
211             }
212         }
213     }
214
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);
218
219         for (String variable : result.keySet()) {
220             onValueReceived(variable, result.get(variable), serviceId);
221         }
222
223         return result;
224     }
225
226     private void setSourceName(Command command) {
227         Map<String, String> result = updateResourceState("MainTVAgent2", "GetSourceList", null);
228
229         String source = command.toString();
230         String id = null;
231
232         String resultResult = result.get("Result");
233         if ("OK".equals(resultResult)) {
234             String xml = result.get("SourceList");
235             if (xml != null) {
236                 id = parseSourceList(xml).get(source);
237             }
238         } else {
239             logger.warn("Source list query failed, result='{}'", resultResult);
240         }
241
242         if (source != null && id != null) {
243             result = updateResourceState("MainTVAgent2", "SetMainTVSource",
244                     SamsungTvUtils.buildHashMap("Source", source, "ID", id, "UiID", "0"));
245
246             resultResult = result.get("Result");
247             if ("OK".equals(resultResult)) {
248                 logger.debug("Command successfully executed");
249             } else {
250                 logger.warn("Command execution failed, result='{}'", resultResult);
251             }
252         } else {
253             logger.warn("Source id for '{}' couldn't be found", command.toString());
254         }
255     }
256
257     private void setBrowserUrl(Command command) {
258         Map<String, String> result = updateResourceState("MainTVAgent2", "RunBrowser",
259                 SamsungTvUtils.buildHashMap("BrowserURL", command.toString()));
260
261         String resultResult = result.get("Result");
262         if ("OK".equals(resultResult)) {
263             logger.debug("Command successfully executed");
264         } else {
265             logger.warn("Command execution failed, result='{}'", resultResult);
266         }
267     }
268
269     private void stopBrowser(Command command) {
270         Map<String, String> result = updateResourceState("MainTVAgent2", "StopBrowser", null);
271
272         String resultResult = result.get("Result");
273         if ("OK".equals(resultResult)) {
274             logger.debug("Command successfully executed");
275         } else {
276             logger.warn("Command execution failed, result='{}'", resultResult);
277         }
278     }
279
280     private @Nullable String parseCurrentChannel(@Nullable String xml) {
281         String majorCh = null;
282
283         if (xml != null) {
284             Document dom = SamsungTvUtils.loadXMLFromString(xml);
285
286             if (dom != null) {
287                 NodeList nodeList = dom.getDocumentElement().getElementsByTagName("MajorCh");
288
289                 if (nodeList != null) {
290                     majorCh = nodeList.item(0).getFirstChild().getNodeValue();
291                 }
292             }
293         }
294
295         return majorCh;
296     }
297
298     private Map<String, String> parseSourceList(String xml) {
299         Map<String, String> list = new HashMap<>();
300
301         Document dom = SamsungTvUtils.loadXMLFromString(xml);
302
303         if (dom != null) {
304             NodeList nodeList = dom.getDocumentElement().getElementsByTagName("Source");
305
306             if (nodeList != null) {
307                 for (int i = 0; i < nodeList.getLength(); i++) {
308
309                     String sourceType = null;
310                     String id = null;
311
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();
316                     }
317                     l = element.getElementsByTagName("ID");
318                     if (l != null && l.getLength() > 0) {
319                         id = l.item(0).getFirstChild().getNodeValue();
320                     }
321
322                     if (sourceType != null && id != null) {
323                         list.put(sourceType, id);
324                     }
325                 }
326             }
327         }
328
329         return list;
330     }
331
332     @Override
333     public void onStatusChanged(boolean status) {
334         logger.debug("onStatusChanged: status={}", status);
335     }
336 }