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