]> git.basschouten.com Git - openhab-addons.git/blob
e175b22273d0583a9f2565dcf2644b932ed069be
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.soap;
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.List;
21 import java.util.Optional;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
25 import java.util.stream.Collectors;
26
27 import javax.xml.soap.SOAPMessage;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.eclipse.jetty.client.api.ContentResponse;
33 import org.openhab.binding.tr064.internal.config.Tr064ChannelConfig;
34 import org.openhab.binding.tr064.internal.dto.additions.Call;
35 import org.openhab.binding.tr064.internal.dto.additions.Root;
36 import org.openhab.binding.tr064.internal.util.Util;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import com.google.gson.Gson;
48 import com.google.gson.GsonBuilder;
49
50 /**
51  * The {@link SOAPValueConverter} converts SOAP values and openHAB states
52  *
53  * @author Jan N. Klug - Initial contribution
54  */
55 @NonNullByDefault
56 public class SOAPValueConverter {
57     private final Logger logger = LoggerFactory.getLogger(SOAPValueConverter.class);
58     private final HttpClient httpClient;
59
60     public SOAPValueConverter(HttpClient httpClient) {
61         this.httpClient = httpClient;
62     }
63
64     /**
65      * convert an openHAB command to a SOAP value
66      *
67      * @param command the command to be converted
68      * @param dataType the datatype to send
69      * @param unit if available, the unit of the converted value
70      * @return a string optional containing the converted value
71      */
72     public Optional<String> getSOAPValueFromCommand(Command command, String dataType, String unit) {
73         if (dataType.isEmpty()) {
74             // we don't have data to send
75             return Optional.of("");
76         }
77         if (command instanceof QuantityType) {
78             QuantityType<?> value = (unit.isEmpty()) ? ((QuantityType<?>) command)
79                     : ((QuantityType<?>) command).toUnit(unit);
80             if (value == null) {
81                 logger.warn("Could not convert {} to unit {}", command, unit);
82                 return Optional.empty();
83             }
84             switch (dataType) {
85                 case "ui1":
86                 case "ui2":
87                     return Optional.of(String.valueOf(value.shortValue()));
88                 case "i4":
89                 case "ui4":
90                     return Optional.of(String.valueOf(value.intValue()));
91                 default:
92             }
93         } else if (command instanceof DecimalType) {
94             BigDecimal value = ((DecimalType) command).toBigDecimal();
95             switch (dataType) {
96                 case "ui1":
97                 case "ui2":
98                     return Optional.of(String.valueOf(value.shortValue()));
99                 case "i4":
100                 case "ui4":
101                     return Optional.of(String.valueOf(value.intValue()));
102                 default:
103             }
104         } else if (command instanceof StringType) {
105             if (dataType.equals("string")) {
106                 return Optional.of(command.toString());
107             }
108         } else if (command instanceof OnOffType) {
109             if (dataType.equals("boolean")) {
110                 return Optional.of(OnOffType.ON.equals(command) ? "1" : "0");
111             }
112         }
113         return Optional.empty();
114     }
115
116     /**
117      * convert the value from a SOAP message to an openHAB value
118      *
119      * @param soapMessage the inbound SOAP message
120      * @param element the element that needs to be extracted
121      * @param channelConfig the channel config containing additional information (if null a data-type "string" and
122      *            missing unit is assumed)
123      * @return an Optional of State containing the converted value
124      */
125     public Optional<State> getStateFromSOAPValue(SOAPMessage soapMessage, String element,
126             @Nullable Tr064ChannelConfig channelConfig) {
127         String dataType = channelConfig != null ? channelConfig.getDataType() : "string";
128         String unit = channelConfig != null ? channelConfig.getChannelTypeDescription().getItem().getUnit() : "";
129
130         return getSOAPElement(soapMessage, element).map(rawValue -> {
131             // map rawValue to State
132             switch (dataType) {
133                 case "boolean":
134                     return rawValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
135                 case "string":
136                     return new StringType(rawValue);
137                 case "ui1":
138                 case "ui2":
139                 case "i4":
140                 case "ui4":
141                     if (!unit.isEmpty()) {
142                         return new QuantityType<>(rawValue + " " + unit);
143                     } else {
144                         return new DecimalType(rawValue);
145                     }
146                 default:
147                     return null;
148             }
149         }).map(state -> {
150             // check if we need post processing
151             if (channelConfig == null
152                     || channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor() == null) {
153                 return state;
154             }
155             String postProcessor = channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor();
156             try {
157                 Method method = SOAPValueConverter.class.getDeclaredMethod(postProcessor, State.class,
158                         Tr064ChannelConfig.class);
159                 Object o = method.invoke(this, state, channelConfig);
160                 if (o instanceof State) {
161                     return (State) o;
162                 }
163             } catch (NoSuchMethodException | IllegalAccessException e) {
164                 logger.warn("Postprocessor {} not found, this most likely is a programming error", postProcessor, e);
165             } catch (InvocationTargetException e) {
166                 Throwable cause = e.getCause();
167                 logger.info("Postprocessor {} failed: {}", postProcessor,
168                         cause != null ? cause.getMessage() : e.getMessage());
169             }
170             return null;
171         }).or(Optional::empty);
172     }
173
174     /**
175      * post processor to map mac device signal strength to system.signal-strength 0-4
176      *
177      * @param state with signalStrength
178      * @param channelConfig channel config of the mac signal strength
179      * @return the mapped system.signal-strength in range 0-4
180      */
181     @SuppressWarnings("unused")
182     private State processMacSignalStrength(State state, Tr064ChannelConfig channelConfig) {
183         State mappedSignalStrength = UnDefType.UNDEF;
184         DecimalType currentStateValue = state.as(DecimalType.class);
185
186         if (currentStateValue != null) {
187             if (currentStateValue.intValue() > 80) {
188                 mappedSignalStrength = new DecimalType(4);
189             } else if (currentStateValue.intValue() > 60) {
190                 mappedSignalStrength = new DecimalType(3);
191             } else if (currentStateValue.intValue() > 40) {
192                 mappedSignalStrength = new DecimalType(2);
193             } else if (currentStateValue.intValue() > 20) {
194                 mappedSignalStrength = new DecimalType(1);
195             } else {
196                 mappedSignalStrength = new DecimalType(0);
197             }
198         }
199
200         return mappedSignalStrength;
201     }
202
203     /**
204      * post processor for answering machine new messages channel
205      *
206      * @param state the message list URL
207      * @param channelConfig channel config of the TAM new message channel
208      * @return the number of new messages
209      * @throws PostProcessingException if the message list could not be retrieved
210      */
211     @SuppressWarnings("unused")
212     private State processTamListURL(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
213         try {
214             ContentResponse response = httpClient.newRequest(state.toString()).timeout(1000, TimeUnit.MILLISECONDS)
215                     .send();
216             String responseContent = response.getContentAsString();
217             int messageCount = responseContent.split("<New>1</New>").length - 1;
218
219             return new DecimalType(messageCount);
220         } catch (InterruptedException | TimeoutException | ExecutionException e) {
221             throw new PostProcessingException("Failed to get TAM list from URL " + state.toString(), e);
222         }
223     }
224
225     /**
226      * post processor for missed calls
227      *
228      * @param state the call list URL
229      * @param channelConfig channel config of the missed call channel (contains day number)
230      * @return the number of missed calls
231      * @throws PostProcessingException if call list could not be retrieved
232      */
233     @SuppressWarnings("unused")
234     private State processMissedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
235         return processCallList(state, channelConfig.getParameter(), CallListType.MISSED_COUNT);
236     }
237
238     /**
239      * post processor for inbound calls
240      *
241      * @param state the call list URL
242      * @param channelConfig channel config of the inbound call channel (contains day number)
243      * @return the number of inbound calls
244      * @throws PostProcessingException if call list could not be retrieved
245      */
246     @SuppressWarnings("unused")
247     private State processInboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
248         return processCallList(state, channelConfig.getParameter(), CallListType.INBOUND_COUNT);
249     }
250
251     /**
252      * post processor for rejected calls
253      *
254      * @param state the call list URL
255      * @param channelConfig channel config of the rejected call channel (contains day number)
256      * @return the number of rejected calls
257      * @throws PostProcessingException if call list could not be retrieved
258      */
259     @SuppressWarnings("unused")
260     private State processRejectedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
261         return processCallList(state, channelConfig.getParameter(), CallListType.REJECTED_COUNT);
262     }
263
264     /**
265      * post processor for outbound calls
266      *
267      * @param state the call list URL
268      * @param channelConfig channel config of the outbound call channel (contains day number)
269      * @return the number of outbound calls
270      * @throws PostProcessingException if call list could not be retrieved
271      */
272     @SuppressWarnings("unused")
273     private State processOutboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
274         return processCallList(state, channelConfig.getParameter(), CallListType.OUTBOUND_COUNT);
275     }
276
277     /**
278      * post processor for JSON call list
279      *
280      * @param state the call list URL
281      * @param channelConfig channel config of the call list channel (contains day number)
282      * @return caller list in JSON format
283      * @throws PostProcessingException if call list could not be retrieved
284      */
285     @SuppressWarnings("unused")
286     private State processCallListJSON(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
287         return processCallList(state, channelConfig.getParameter(), CallListType.JSON_LIST);
288     }
289
290     /**
291      * internal helper for call list post processors
292      *
293      * @param state the call list URL
294      * @param days number of days to get
295      * @param type type of call (2=missed 1=inbound 4=rejected 3=outbund)
296      * @return the quantity of calls of the given type within the given number of days
297      * @throws PostProcessingException if the call list could not be retrieved
298      */
299     private State processCallList(State state, @Nullable String days, CallListType type)
300             throws PostProcessingException {
301         Root callListRoot = Util.getAndUnmarshalXML(httpClient, state.toString() + "&days=" + days, Root.class);
302         if (callListRoot == null) {
303             throw new PostProcessingException("Failed to get call list from URL " + state.toString());
304         }
305         List<Call> calls = callListRoot.getCall();
306         switch (type) {
307             case INBOUND_COUNT:
308             case MISSED_COUNT:
309             case OUTBOUND_COUNT:
310             case REJECTED_COUNT:
311                 long callCount = calls.stream().filter(call -> type.typeString().equals(call.getType())).count();
312                 return new DecimalType(callCount);
313             case JSON_LIST:
314                 Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssX").serializeNulls().create();
315                 List<CallListEntry> callListEntries = calls.stream().map(CallListEntry::new)
316                         .collect(Collectors.toList());
317                 return new StringType(gson.toJson(callListEntries));
318         }
319         return UnDefType.UNDEF;
320     }
321 }