]> git.basschouten.com Git - openhab-addons.git/blob
abf829d93b23f069610d487beafd949a99b8706b
[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.helios.internal.handler;
14
15 import static org.openhab.binding.helios.internal.HeliosBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.text.SimpleDateFormat;
19 import java.util.ArrayList;
20 import java.util.Calendar;
21 import java.util.GregorianCalendar;
22 import java.util.List;
23 import java.util.concurrent.RejectedExecutionException;
24 import java.util.concurrent.TimeUnit;
25
26 import javax.jws.WebParam;
27 import javax.jws.WebService;
28 import javax.xml.bind.JAXBContext;
29 import javax.xml.bind.JAXBElement;
30 import javax.xml.bind.JAXBException;
31 import javax.xml.namespace.QName;
32 import javax.xml.transform.dom.DOMResult;
33 import javax.xml.ws.BindingProvider;
34 import javax.xml.ws.BindingType;
35 import javax.xml.ws.Endpoint;
36 import javax.xml.ws.Service;
37 import javax.xml.ws.WebServiceException;
38 import javax.xml.ws.handler.Handler;
39 import javax.xml.ws.wsaddressing.W3CEndpointReference;
40 import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;
41
42 import org.apache.cxf.helpers.DOMUtils;
43 import org.apache.cxf.wsn.wsdl.WSNWSDLLocator;
44 import org.oasis_open.docs.wsn.b_2.FilterType;
45 import org.oasis_open.docs.wsn.b_2.Notify;
46 import org.oasis_open.docs.wsn.b_2.Renew;
47 import org.oasis_open.docs.wsn.b_2.RenewResponse;
48 import org.oasis_open.docs.wsn.b_2.Subscribe;
49 import org.oasis_open.docs.wsn.b_2.SubscribeResponse;
50 import org.oasis_open.docs.wsn.b_2.TopicExpressionType;
51 import org.oasis_open.docs.wsn.b_2.Unsubscribe;
52 import org.oasis_open.docs.wsn.b_2.UnsubscribeResponse;
53 import org.oasis_open.docs.wsn.bw_2.InvalidFilterFault;
54 import org.oasis_open.docs.wsn.bw_2.InvalidMessageContentExpressionFault;
55 import org.oasis_open.docs.wsn.bw_2.InvalidProducerPropertiesExpressionFault;
56 import org.oasis_open.docs.wsn.bw_2.InvalidTopicExpressionFault;
57 import org.oasis_open.docs.wsn.bw_2.NotificationConsumer;
58 import org.oasis_open.docs.wsn.bw_2.NotificationProducer;
59 import org.oasis_open.docs.wsn.bw_2.NotifyMessageNotSupportedFault;
60 import org.oasis_open.docs.wsn.bw_2.PausableSubscriptionManager;
61 import org.oasis_open.docs.wsn.bw_2.SubscribeCreationFailedFault;
62 import org.oasis_open.docs.wsn.bw_2.TopicExpressionDialectUnknownFault;
63 import org.oasis_open.docs.wsn.bw_2.TopicNotSupportedFault;
64 import org.oasis_open.docs.wsn.bw_2.UnableToDestroySubscriptionFault;
65 import org.oasis_open.docs.wsn.bw_2.UnacceptableInitialTerminationTimeFault;
66 import org.oasis_open.docs.wsn.bw_2.UnacceptableTerminationTimeFault;
67 import org.oasis_open.docs.wsn.bw_2.UnrecognizedPolicyRequestFault;
68 import org.oasis_open.docs.wsn.bw_2.UnsupportedPolicyRequestFault;
69 import org.oasis_open.docs.wsrf.rw_2.ResourceUnknownFault;
70 import org.openhab.binding.helios.internal.ws.soap.SOAPActionHandler;
71 import org.openhab.binding.helios.internal.ws.soap.SOAPCallStateChanged;
72 import org.openhab.binding.helios.internal.ws.soap.SOAPCardEntered;
73 import org.openhab.binding.helios.internal.ws.soap.SOAPCodeEntered;
74 import org.openhab.binding.helios.internal.ws.soap.SOAPDeviceState;
75 import org.openhab.binding.helios.internal.ws.soap.SOAPEvent;
76 import org.openhab.binding.helios.internal.ws.soap.SOAPKeyPressed;
77 import org.openhab.binding.helios.internal.ws.soap.SOAPObjectFactory;
78 import org.openhab.binding.helios.internal.ws.soap.SOAPSubscriptionActionHandler;
79 import org.openhab.core.library.types.DateTimeType;
80 import org.openhab.core.library.types.StringType;
81 import org.openhab.core.thing.ChannelUID;
82 import org.openhab.core.thing.Thing;
83 import org.openhab.core.thing.ThingStatus;
84 import org.openhab.core.thing.ThingStatusDetail;
85 import org.openhab.core.thing.binding.BaseThingHandler;
86 import org.openhab.core.types.Command;
87 import org.openhab.core.types.RefreshType;
88 import org.slf4j.Logger;
89 import org.slf4j.LoggerFactory;
90 import org.w3c.dom.Element;
91 import org.w3c.dom.NodeList;
92
93 /**
94  * The {@link HeliosHandler27} is responsible for handling commands, which are
95  * sent to one of the channels.
96  *
97  * @author Karel Goderis - Initial contribution
98  */
99 @WebService(endpointInterface = "org.oasis_open.docs.wsn.bw_2.NotificationConsumer")
100 @BindingType(javax.xml.ws.soap.SOAPBinding.SOAP12HTTP_BINDING)
101 public class HeliosHandler27 extends BaseThingHandler implements NotificationConsumer {
102
103     // List of Configuration constants
104     public static final String IP_ADDRESS = "ipAddress";
105     public static final String OPENHAB_IP_ADDRESS = "openHABipAddress";
106     public static final String OPENHAB_PORT_NUMBER = "openHABportNumber";
107
108     private final Logger logger = LoggerFactory.getLogger(HeliosHandler27.class);
109
110     private static final String SUBSCRIPTION_PERIOD = "PT1H";
111
112     private JAXBContext context = null;
113     private Endpoint endpoint = null;
114     private NotificationProducer notificationProducer = null;
115     private PausableSubscriptionManager subscription = null;
116     private GregorianCalendar currentTime;
117     private GregorianCalendar terminationTime;
118     private W3CEndpointReference subscriptionReference;
119     private String subscriptionID;
120     private SOAPEvent previousEvent = null;
121
122     public static final String HELIOS_URI = "http://www.2n.cz/2013/event";
123     public static final String DIALECT_URI = "http://www.2n.cz/2013/TopicExpression/Multiple";
124     public static final String WSN_URI = "http://docs.oasis-open.org/wsn/b-2";
125
126     public static final QName TOPIC_EXPRESSION = new QName(WSN_URI, "TopicExpression");
127     public static final QName INITIAL_TERMINATION_TIME = new QName(WSN_URI, "InitialTerminationTime");
128     public static final QName MAXIMUM_NUMBER = new QName(HELIOS_URI, "MaximumNumber");
129     public static final QName SIMPLE_MESSAGES = new QName(HELIOS_URI, "SimpleMessages");
130     public static final QName START_TIME_STAMP = new QName(HELIOS_URI, "StartTimestamp");
131     public static final QName START_RECORD_ID = new QName(HELIOS_URI, "StartRecordId");
132
133     public HeliosHandler27(Thing thing) {
134         super(thing);
135     }
136
137     @Override
138     public void handleCommand(ChannelUID channelUID, Command command) {
139         if (!(command instanceof RefreshType)) {
140             // 2N.cz has not yet released the automation part of the Helios IP HTTP/SOAP based API. Only the
141             // notification part has been documented, so for now there is nothing to do
142             // here
143             logger.debug("The Helios IP is a read-only device and can not handle commands");
144         }
145     }
146
147     public String getSubscriptionID() {
148         return subscriptionID;
149     }
150
151     @SuppressWarnings("rawtypes")
152     @Override
153     public void initialize() {
154         logger.debug("Initializing Helios IP handler.");
155         List<Handler> handlerChain = new ArrayList<>();
156         handlerChain.add(new SOAPActionHandler());
157
158         try {
159             context = JAXBContext.newInstance(SOAPObjectFactory.class, SOAPEvent.class, SOAPKeyPressed.class,
160                     SOAPCallStateChanged.class, SOAPCodeEntered.class, SOAPCardEntered.class, SOAPDeviceState.class);
161         } catch (JAXBException e) {
162             logger.error("An exception occurred while setting up the JAXB Context factory: {}", e.getMessage(), e);
163             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
164             throw new RuntimeException();
165         }
166
167         try {
168             if (endpoint == null || (endpoint != null && !endpoint.isPublished())) {
169                 String address = "http://" + (String) getConfig().get(OPENHAB_IP_ADDRESS) + ":"
170                         + ((BigDecimal) getConfig().get(OPENHAB_PORT_NUMBER)).toString() + "/notification"
171                         + System.currentTimeMillis();
172                 logger.debug("Publishing the notification consumer webservice on '{}", address);
173                 endpoint = Endpoint.publish(address, this);
174                 ((javax.xml.ws.soap.SOAPBinding) endpoint.getBinding()).setHandlerChain(handlerChain);
175             }
176         } catch (WebServiceException e1) {
177             logger.debug("An exception occurred while setting up the notification consumer webservice: {}",
178                     e1.getMessage(), e1);
179             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
180             return;
181         }
182
183         try {
184             String heliosAddress = "http://" + (String) getConfig().get(IP_ADDRESS) + "/notification";
185             Service notificationProducerService = Service.create(WSNWSDLLocator.getWSDLUrl(),
186                     new QName("http://cxf.apache.org/wsn/jaxws", "NotificationProducerService"));
187             notificationProducer = notificationProducerService.getPort(
188                     new W3CEndpointReferenceBuilder().address(heliosAddress).build(), NotificationProducer.class);
189             ((BindingProvider) notificationProducer).getBinding().setHandlerChain(handlerChain);
190         } catch (WebServiceException e1) {
191             logger.debug("An exception occurred while setting up the notification webservice client: {}",
192                     e1.getMessage(), e1);
193             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
194             return;
195         }
196
197         try {
198             // set up the access to the subscription manager on the Helios IP
199             // Vario so that we can renew in the future
200             String heliosAddress = "http://" + (String) getConfig().get(IP_ADDRESS) + "/notification";
201             Service subscriptionService = Service.create(WSNWSDLLocator.getWSDLUrl(),
202                     new QName("http://cxf.apache.org/wsn/jaxws", "PausableSubscriptionManagerService"));
203             subscription = subscriptionService.getPort(new W3CEndpointReferenceBuilder().address(heliosAddress).build(),
204                     PausableSubscriptionManager.class);
205
206             handlerChain = new ArrayList<>();
207             handlerChain.add(new SOAPSubscriptionActionHandler(this));
208             ((BindingProvider) subscription).getBinding().setHandlerChain(handlerChain);
209         } catch (WebServiceException e1) {
210             logger.debug("An exception occurred while setting up the subscription manager client: {}", e1.getMessage(),
211                     e1);
212             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
213             return;
214         }
215
216         try {
217             subscribe(endpoint.getEndpointReference(W3CEndpointReference.class));
218         } catch (WebServiceException | TopicNotSupportedFault | InvalidFilterFault | TopicExpressionDialectUnknownFault
219                 | UnacceptableInitialTerminationTimeFault | SubscribeCreationFailedFault
220                 | InvalidMessageContentExpressionFault | InvalidTopicExpressionFault | UnrecognizedPolicyRequestFault
221                 | UnsupportedPolicyRequestFault | ResourceUnknownFault | NotifyMessageNotSupportedFault
222                 | InvalidProducerPropertiesExpressionFault e) {
223             logger.debug("An exception occurred while subscribing to the notifications for thing '{}': {}",
224                     getThing().getUID(), e.getMessage(), e);
225             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
226             return;
227         }
228
229         updateStatus(ThingStatus.ONLINE);
230     }
231
232     @Override
233     public void dispose() {
234         logger.debug("Disposing Helios IP handler.");
235
236         try {
237             unsubscribe();
238             if (endpoint != null) {
239                 endpoint.stop();
240             }
241             endpoint = null;
242             terminationTime = null;
243             context = null;
244             notificationProducer = null;
245             subscription = null;
246         } catch (Exception e) {
247             logger.error("An exception occurred while disposing the Helios Thing Handler : {}", e.getMessage(), e);
248         }
249     }
250
251     @Override
252     public void notify(
253             @WebParam(partName = "Notify", name = "Notify", targetNamespace = "http://docs.oasis-open.org/wsn/b-2") Notify notify) {
254         for (Object object : notify.getAny()) {
255             try {
256                 this.processNotification(context.createUnmarshaller().unmarshal((Element) object, SOAPEvent.class));
257             } catch (JAXBException e) {
258                 logger.error("An exception occurred while processing a notification message : {}", e.getMessage(), e);
259             }
260         }
261     }
262
263     public void processNotification(JAXBElement<SOAPEvent> message) {
264         if (getThing().getStatus() == ThingStatus.ONLINE) {
265             SOAPEvent event = message.getValue();
266             // WS-Notification does not provide a mechanism to query existing
267             // subscriptions established before, so these keep lingering on the
268             // remote device. Therefore, when restarting the OH runtime, we
269             // might receive events more than one time, we need to filter these
270             // out
271             if (previousEvent == null || !previousEvent.equals(event)) {
272                 previousEvent = event;
273
274                 Object data = event.getData();
275
276                 if (data instanceof SOAPKeyPressed) {
277                     StringType valueType = new StringType(((SOAPKeyPressed) data).getKeyCode());
278                     updateState(new ChannelUID(getThing().getUID(), KEY_PRESSED), valueType);
279
280                     DateTimeType stampType = new DateTimeType(event.getTimestamp());
281                     updateState(new ChannelUID(getThing().getUID(), KEY_PRESSED_STAMP), stampType);
282                 }
283
284                 if (data instanceof SOAPCallStateChanged) {
285                     StringType valueType = new StringType(((SOAPCallStateChanged) data).getState());
286                     updateState(new ChannelUID(getThing().getUID(), CALL_STATE), valueType);
287
288                     valueType = new StringType(((SOAPCallStateChanged) data).getDirection());
289                     updateState(new ChannelUID(getThing().getUID(), CALL_DIRECTION), valueType);
290
291                     DateTimeType stampType = new DateTimeType(event.getTimestamp());
292                     updateState(new ChannelUID(getThing().getUID(), CALL_STATE_STAMP), stampType);
293                 }
294
295                 if (data instanceof SOAPCardEntered) {
296                     StringType valueType = new StringType(((SOAPCardEntered) data).getCard());
297                     updateState(new ChannelUID(getThing().getUID(), CARD), valueType);
298
299                     valueType = new StringType(((SOAPCardEntered) data).getValid());
300                     updateState(new ChannelUID(getThing().getUID(), CARD_VALID), valueType);
301
302                     DateTimeType stampType = new DateTimeType(event.getTimestamp());
303                     updateState(new ChannelUID(getThing().getUID(), CARD_STAMP), stampType);
304                 }
305
306                 if (data instanceof SOAPCodeEntered) {
307                     StringType valueType = new StringType(((SOAPCodeEntered) data).getCode());
308                     updateState(new ChannelUID(getThing().getUID(), CODE), valueType);
309
310                     valueType = new StringType(((SOAPCodeEntered) data).getValid());
311                     updateState(new ChannelUID(getThing().getUID(), CODE_VALID), valueType);
312
313                     DateTimeType stampType = new DateTimeType(event.getTimestamp());
314                     updateState(new ChannelUID(getThing().getUID(), CODE_STAMP), stampType);
315                 }
316
317                 if (data instanceof SOAPDeviceState) {
318                     StringType valueType = new StringType(((SOAPDeviceState) data).getState());
319                     updateState(new ChannelUID(getThing().getUID(), DEVICE_STATE), valueType);
320
321                     DateTimeType stampType = new DateTimeType(event.getTimestamp());
322                     updateState(new ChannelUID(getThing().getUID(), DEVICE_STATE_STAMP), stampType);
323                 }
324             } else {
325                 logger.warn("Duplicate event received due to lingering subscriptions: '{}':'{}'", event.getEventName(),
326                         event.getTimestamp());
327             }
328         }
329     }
330
331     public void renew(String newTerminationTime) throws ResourceUnknownFault, UnacceptableTerminationTimeFault {
332         if (subscription != null) {
333             Renew renew = new Renew();
334             renew.setTerminationTime(newTerminationTime);
335
336             RenewResponse response = subscription.renew(renew);
337             currentTime = response.getCurrentTime().toGregorianCalendar();
338             terminationTime = response.getTerminationTime().toGregorianCalendar();
339
340             SimpleDateFormat pFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
341             logger.debug("Renewed the subscription with ID '{}' for '{}' from {} until {}",
342                     new Object[] { getSubscriptionID(), getThing().getUID(), pFormatter.format(currentTime.getTime()),
343                             pFormatter.format(terminationTime.getTime()) });
344         }
345     }
346
347     public void unsubscribe() {
348         if (subscription != null) {
349             try {
350                 UnsubscribeResponse response = subscription.unsubscribe(new Unsubscribe());
351                 logger.debug("Unsubscribing the subscription with ID '{}' for '{}' ", getSubscriptionID(),
352                         getThing().getUID());
353             } catch (UnableToDestroySubscriptionFault | ResourceUnknownFault e) {
354                 logger.error("An exception occurred while unsubscribing from the subscription : {}", e.getMessage(), e);
355             }
356         }
357     }
358
359     public void subscribe(W3CEndpointReference epr)
360             throws TopicNotSupportedFault, InvalidFilterFault, TopicExpressionDialectUnknownFault,
361             UnacceptableInitialTerminationTimeFault, SubscribeCreationFailedFault, InvalidMessageContentExpressionFault,
362             InvalidTopicExpressionFault, UnrecognizedPolicyRequestFault, UnsupportedPolicyRequestFault,
363             ResourceUnknownFault, NotifyMessageNotSupportedFault, InvalidProducerPropertiesExpressionFault {
364         if (notificationProducer != null) {
365             Subscribe subscribeRequest = new Subscribe();
366             subscribeRequest.setConsumerReference(epr);
367             subscribeRequest.setFilter(new FilterType());
368
369             TopicExpressionType topicExp = new TopicExpressionType();
370             topicExp.getContent().add("");
371             topicExp.setDialect(DIALECT_URI);
372             subscribeRequest.getFilter().getAny()
373                     .add(new JAXBElement<>(TOPIC_EXPRESSION, TopicExpressionType.class, topicExp));
374
375             subscribeRequest.setInitialTerminationTime(
376                     new JAXBElement<>(INITIAL_TERMINATION_TIME, String.class, SUBSCRIPTION_PERIOD));
377
378             subscribeRequest.setSubscriptionPolicy(new Subscribe.SubscriptionPolicy());
379             subscribeRequest.getSubscriptionPolicy().getAny().add(new JAXBElement<>(MAXIMUM_NUMBER, Integer.class, 1));
380             subscribeRequest.getSubscriptionPolicy().getAny().add(new JAXBElement<>(SIMPLE_MESSAGES, Integer.class, 1));
381             GregorianCalendar now = new GregorianCalendar();
382             SimpleDateFormat pFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
383             subscribeRequest.getSubscriptionPolicy().getAny()
384                     .add(new JAXBElement<>(START_TIME_STAMP, String.class, pFormatter.format(now.getTime())));
385
386             SubscribeResponse response = notificationProducer.subscribe(subscribeRequest);
387
388             if (response != null) {
389                 currentTime = response.getCurrentTime().toGregorianCalendar();
390                 terminationTime = response.getTerminationTime().toGregorianCalendar();
391                 subscriptionReference = response.getSubscriptionReference();
392
393                 Element element = DOMUtils.createDocument().createElement("elem");
394                 subscriptionReference.writeTo(new DOMResult(element));
395                 NodeList nl = element.getElementsByTagNameNS("http://www.2n.cz/2013/event", "SubscriptionId");
396                 if (nl != null && nl.getLength() > 0) {
397                     Element e = (Element) nl.item(0);
398                     subscriptionID = DOMUtils.getContent(e).trim();
399                 }
400                 logger.debug("Established a subscription with ID '{}' for '{}' as from {} until {}",
401                         new Object[] { subscriptionID, getThing().getUID(), pFormatter.format(currentTime.getTime()),
402                                 pFormatter.format(terminationTime.getTime()) });
403
404                 java.util.Calendar triggerTime = terminationTime;
405                 triggerTime.add(Calendar.MINUTE, -1);
406
407                 logger.debug("Scheduling a renewal of the subscription with ID '{}' for '{}' to happen on {}",
408                         new Object[] { subscriptionID, getThing().getUID(), pFormatter.format(triggerTime.getTime()) });
409                 try {
410                     scheduler.schedule(renewRunnable, triggerTime.getTimeInMillis() - System.currentTimeMillis(),
411                             TimeUnit.MILLISECONDS);
412                 } catch (RejectedExecutionException e) {
413                     logger.error("An exception occurred while scheduling a renewal : '{}'", e.getMessage(), e);
414                 }
415             }
416         }
417     }
418
419     protected Runnable renewRunnable = new Runnable() {
420
421         @Override
422         public void run() {
423             SimpleDateFormat pFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
424             boolean result = false;
425
426             try {
427                 ((HeliosHandler27) getThing().getHandler()).renew(SUBSCRIPTION_PERIOD);
428                 result = true;
429             } catch (Exception e) {
430                 logger.error("An exception occurred while renewing the subscription : {}", e.getMessage(), e);
431                 ((HeliosHandler27) getThing().getHandler()).dispose();
432                 ((HeliosHandler27) getThing().getHandler()).initialize();
433                 return;
434             }
435
436             if (result) {
437                 java.util.Calendar triggerTime = terminationTime;
438                 triggerTime.add(Calendar.MINUTE, -1);
439
440                 logger.debug("Scheduling a renewal of the subscription with ID '{}' for '{}' to happen on {}",
441                         new Object[] { subscriptionID, getThing().getUID(), pFormatter.format(triggerTime.getTime()) });
442                 try {
443                     scheduler.schedule(renewRunnable, triggerTime.getTimeInMillis() - System.currentTimeMillis(),
444                             TimeUnit.MILLISECONDS);
445                 } catch (RejectedExecutionException e) {
446                     logger.error("An exception occurred while scheduling a renewal : '{}'", e.getMessage(), e);
447                 }
448             }
449         }
450     };
451 }