2 * Copyright (c) 2010-2020 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.tr064.internal;
15 import static org.openhab.binding.tr064.internal.util.Util.getSOAPElement;
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;
25 import javax.xml.soap.SOAPMessage;
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;
42 * The {@link SOAPValueConverter} converts SOAP values and openHAB states
44 * @author Jan N. Klug - Initial contribution
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;
52 public SOAPValueConverter(HttpClient httpClient) {
53 this.httpClient = httpClient;
57 * convert an openHAB command to a SOAP value
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
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("");
69 if (command instanceof QuantityType) {
70 QuantityType<?> value = (unit.isEmpty()) ? ((QuantityType<?>) command)
71 : ((QuantityType<?>) command).toUnit(unit);
73 logger.warn("Could not convert {} to unit {}", command, unit);
74 return Optional.empty();
78 return Optional.of(String.valueOf(value.shortValue()));
80 return Optional.of(String.valueOf(value.intValue()));
83 } else if (command instanceof DecimalType) {
84 BigDecimal value = ((DecimalType) command).toBigDecimal();
87 return Optional.of(String.valueOf(value.shortValue()));
89 return Optional.of(String.valueOf(value.intValue()));
92 } else if (command instanceof StringType) {
93 if (dataType.equals("string")) {
94 return Optional.of(command.toString());
96 } else if (command instanceof OnOffType) {
97 if (dataType.equals("boolean")) {
98 return Optional.of(OnOffType.ON.equals(command) ? "1" : "0");
101 return Optional.empty();
105 * convert the value from a SOAP message to an openHAB value
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
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() : "";
118 return getSOAPElement(soapMessage, element).map(rawValue -> {
119 // map rawValue to State
122 return rawValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
124 return new StringType(rawValue);
127 if (!unit.isEmpty()) {
128 return new QuantityType<>(rawValue + " " + unit);
130 return new DecimalType(rawValue);
136 // check if we need post processing
137 if (channelConfig == null
138 || channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor() == null) {
141 String postProcessor = channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor();
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) {
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());
157 }).or(Optional::empty);
161 * post processor for answering machine new messages channel
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
168 @SuppressWarnings("unused")
169 private State processTamListURL(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
171 ContentResponse response = httpClient.newRequest(state.toString()).timeout(1000, TimeUnit.MILLISECONDS)
173 String responseContent = response.getContentAsString();
174 int messageCount = responseContent.split("<New>1</New>").length - 1;
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);
183 * post processor for missed calls
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
190 @SuppressWarnings("unused")
191 private State processMissedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
192 return processCallList(state, channelConfig.getParameter(), "2");
196 * post processor for inbound calls
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
203 @SuppressWarnings("unused")
204 private State processInboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
205 return processCallList(state, channelConfig.getParameter(), "1");
209 * post processor for rejected calls
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
216 @SuppressWarnings("unused")
217 private State processRejectedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
218 return processCallList(state, channelConfig.getParameter(), "3");
222 * post processor for outbound calls
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
229 @SuppressWarnings("unused")
230 private State processOutboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
231 return processCallList(state, channelConfig.getParameter(), "4");
235 * internal helper for call list post processors
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
243 private State processCallList(State state, @Nullable String days, String type) throws PostProcessingException {
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;
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);