]> git.basschouten.com Git - openhab-addons.git/blob
214a49b4ecb5b2b6f3c6483370ad422bbe04e05d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.tr064.internal;
14
15 import static org.openhab.binding.tr064.internal.util.Util.getSOAPElement;
16
17 import java.lang.reflect.InvocationTargetException;
18 import java.lang.reflect.Method;
19 import java.math.BigDecimal;
20 import java.util.Optional;
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.TimeoutException;
24
25 import javax.xml.soap.SOAPMessage;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.eclipse.jetty.client.HttpClient;
30 import org.eclipse.jetty.client.api.ContentResponse;
31 import org.openhab.binding.tr064.internal.config.Tr064ChannelConfig;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.State;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * The {@link SOAPValueConverter} converts SOAP values and openHAB states
43  *
44  * @author Jan N. Klug - Initial contribution
45  */
46 @NonNullByDefault
47 public class SOAPValueConverter {
48     private static final int REQUEST_TIMEOUT = 5000; // in ms
49     private final Logger logger = LoggerFactory.getLogger(SOAPValueConverter.class);
50     private final HttpClient httpClient;
51
52     public SOAPValueConverter(HttpClient httpClient) {
53         this.httpClient = httpClient;
54     }
55
56     /**
57      * convert an openHAB command to a SOAP value
58      *
59      * @param command the command to be converted
60      * @param dataType the datatype to send
61      * @param unit if available, the unit of the converted value
62      * @return a string optional containing the converted value
63      */
64     public Optional<String> getSOAPValueFromCommand(Command command, String dataType, String unit) {
65         if (dataType.isEmpty()) {
66             // we don't have data to send
67             return Optional.of("");
68         }
69         if (command instanceof QuantityType) {
70             QuantityType<?> value = (unit.isEmpty()) ? ((QuantityType<?>) command)
71                     : ((QuantityType<?>) command).toUnit(unit);
72             if (value == null) {
73                 logger.warn("Could not convert {} to unit {}", command, unit);
74                 return Optional.empty();
75             }
76             switch (dataType) {
77                 case "ui2":
78                     return Optional.of(String.valueOf(value.shortValue()));
79                 case "ui4":
80                     return Optional.of(String.valueOf(value.intValue()));
81                 default:
82             }
83         } else if (command instanceof DecimalType) {
84             BigDecimal value = ((DecimalType) command).toBigDecimal();
85             switch (dataType) {
86                 case "ui2":
87                     return Optional.of(String.valueOf(value.shortValue()));
88                 case "ui4":
89                     return Optional.of(String.valueOf(value.intValue()));
90                 default:
91             }
92         } else if (command instanceof StringType) {
93             if (dataType.equals("string")) {
94                 return Optional.of(command.toString());
95             }
96         } else if (command instanceof OnOffType) {
97             if (dataType.equals("boolean")) {
98                 return Optional.of(OnOffType.ON.equals(command) ? "1" : "0");
99             }
100         }
101         return Optional.empty();
102     }
103
104     /**
105      * convert the value from a SOAP message to an openHAB value
106      *
107      * @param soapMessage the inbound SOAP message
108      * @param element the element that needs to be extracted
109      * @param channelConfig the channel config containing additional information (if null a data-type "string" and
110      *            missing unit is assumed)
111      * @return an Optional of State containing the converted value
112      */
113     public Optional<State> getStateFromSOAPValue(SOAPMessage soapMessage, String element,
114             @Nullable Tr064ChannelConfig channelConfig) {
115         String dataType = channelConfig != null ? channelConfig.getDataType() : "string";
116         String unit = channelConfig != null ? channelConfig.getChannelTypeDescription().getItem().getUnit() : "";
117
118         return getSOAPElement(soapMessage, element).map(rawValue -> {
119             // map rawValue to State
120             switch (dataType) {
121                 case "boolean":
122                     return rawValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
123                 case "string":
124                     return new StringType(rawValue);
125                 case "ui2":
126                 case "ui4":
127                     if (!unit.isEmpty()) {
128                         return new QuantityType<>(rawValue + " " + unit);
129                     } else {
130                         return new DecimalType(rawValue);
131                     }
132                 default:
133                     return null;
134             }
135         }).map(state -> {
136             // check if we need post processing
137             if (channelConfig == null
138                     || channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor() == null) {
139                 return state;
140             }
141             String postProcessor = channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor();
142             try {
143                 Method method = SOAPValueConverter.class.getDeclaredMethod(postProcessor, State.class,
144                         Tr064ChannelConfig.class);
145                 Object o = method.invoke(this, state, channelConfig);
146                 if (o instanceof State) {
147                     return (State) o;
148                 }
149             } catch (NoSuchMethodException | IllegalAccessException e) {
150                 logger.warn("Postprocessor {} not found, this most likely is a programming error", postProcessor, e);
151             } catch (InvocationTargetException e) {
152                 Throwable cause = e.getCause();
153                 logger.info("Postprocessor {} failed: {}", postProcessor,
154                         cause != null ? cause.getMessage() : e.getMessage());
155             }
156             return null;
157         }).or(Optional::empty);
158     }
159
160     /**
161      * post processor for answering machine new messages channel
162      *
163      * @param state the message list URL
164      * @param channelConfig channel config of the TAM new message channel
165      * @return the number of new messages
166      * @throws PostProcessingException if the message list could not be retrieved
167      */
168     @SuppressWarnings("unused")
169     private State processTamListURL(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
170         try {
171             ContentResponse response = httpClient.newRequest(state.toString()).timeout(1000, TimeUnit.MILLISECONDS)
172                     .send();
173             String responseContent = response.getContentAsString();
174             int messageCount = responseContent.split("<New>1</New>").length - 1;
175
176             return new DecimalType(messageCount);
177         } catch (InterruptedException | TimeoutException | ExecutionException e) {
178             throw new PostProcessingException("Failed to get TAM list from URL " + state.toString(), e);
179         }
180     }
181
182     /**
183      * post processor for missed calls
184      *
185      * @param state the call list URL
186      * @param channelConfig channel config of the missed call channel (contains day number)
187      * @return the number of missed calls
188      * @throws PostProcessingException if call list could not be retrieved
189      */
190     @SuppressWarnings("unused")
191     private State processMissedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
192         return processCallList(state, channelConfig.getParameter(), "2");
193     }
194
195     /**
196      * post processor for inbound calls
197      *
198      * @param state the call list URL
199      * @param channelConfig channel config of the inbound call channel (contains day number)
200      * @return the number of inbound calls
201      * @throws PostProcessingException if call list could not be retrieved
202      */
203     @SuppressWarnings("unused")
204     private State processInboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
205         return processCallList(state, channelConfig.getParameter(), "1");
206     }
207
208     /**
209      * post processor for rejected calls
210      *
211      * @param state the call list URL
212      * @param channelConfig channel config of the rejected call channel (contains day number)
213      * @return the number of rejected calls
214      * @throws PostProcessingException if call list could not be retrieved
215      */
216     @SuppressWarnings("unused")
217     private State processRejectedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
218         return processCallList(state, channelConfig.getParameter(), "3");
219     }
220
221     /**
222      * post processor for outbound calls
223      *
224      * @param state the call list URL
225      * @param channelConfig channel config of the outbound call channel (contains day number)
226      * @return the number of outbound calls
227      * @throws PostProcessingException if call list could not be retrieved
228      */
229     @SuppressWarnings("unused")
230     private State processOutboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
231         return processCallList(state, channelConfig.getParameter(), "4");
232     }
233
234     /**
235      * internal helper for call list post processors
236      *
237      * @param state the call list URL
238      * @param days number of days to get
239      * @param type type of call (1=missed 2=inbound 3=rejected 4=outbund)
240      * @return the quantity of calls of the given type within the given number of days
241      * @throws PostProcessingException if the call list could not be retrieved
242      */
243     private State processCallList(State state, @Nullable String days, String type) throws PostProcessingException {
244         try {
245             ContentResponse response = httpClient.newRequest(state.toString() + "&days=" + days)
246                     .timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send();
247             String responseContent = response.getContentAsString();
248             int callCount = responseContent.split("<Type>" + type + "</Type>").length - 1;
249
250             return new DecimalType(callCount);
251         } catch (InterruptedException | TimeoutException | ExecutionException e) {
252             throw new PostProcessingException("Failed to get call list from URL " + state.toString(), e);
253         }
254     }
255 }