]> git.basschouten.com Git - openhab-addons.git/blob
3ae1a35d01e64dff4ce6612480f7c715a630e461
[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.openwebnet.internal.handler;
14
15 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
16
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.Optional;
21 import java.util.Scanner;
22 import java.util.Set;
23 import java.util.TreeSet;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
28 import org.openhab.binding.openwebnet.internal.actions.OpenWebNetCENActions;
29 import org.openhab.core.thing.Channel;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingTypeUID;
33 import org.openhab.core.thing.binding.ThingHandlerService;
34 import org.openhab.core.thing.binding.builder.ChannelBuilder;
35 import org.openhab.core.thing.binding.builder.ThingBuilder;
36 import org.openhab.core.thing.type.ChannelKind;
37 import org.openhab.core.thing.type.ChannelTypeUID;
38 import org.openhab.core.types.Command;
39 import org.openwebnet4j.message.BaseOpenMessage;
40 import org.openwebnet4j.message.CEN;
41 import org.openwebnet4j.message.CEN.Pressure;
42 import org.openwebnet4j.message.CENPlusScenario;
43 import org.openwebnet4j.message.CENPlusScenario.CENPlusPressure;
44 import org.openwebnet4j.message.CENScenario;
45 import org.openwebnet4j.message.CENScenario.CENPressure;
46 import org.openwebnet4j.message.FrameException;
47 import org.openwebnet4j.message.Where;
48 import org.openwebnet4j.message.WhereCEN;
49 import org.openwebnet4j.message.Who;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * The {@link OpenWebNetScenarioHandler} is responsible for handling commands/messages for CEN/CEN+ Scenarios. It
55  * extends the abstract {@link OpenWebNetThingHandler}.
56  *
57  * @author Massimo Valla - Initial contribution
58  */
59 @NonNullByDefault
60 public class OpenWebNetScenarioHandler extends OpenWebNetThingHandler {
61
62     private final Logger logger = LoggerFactory.getLogger(OpenWebNetScenarioHandler.class);
63
64     private interface PressEvent {
65         @Override
66         public String toString();
67     }
68
69     private enum CENPressEvent implements PressEvent {
70         CEN_EVENT_START_PRESS("START_PRESS"),
71         CEN_EVENT_SHORT_PRESS("SHORT_PRESS"),
72         CEN_EVENT_EXTENDED_PRESS("EXTENDED_PRESS"),
73         CEN_EVENT_RELEASE_EXTENDED_PRESS("RELEASE_EXTENDED_PRESS");
74
75         private final String press;
76
77         CENPressEvent(final String pr) {
78             this.press = pr;
79         }
80
81         public static @Nullable CENPressEvent fromValue(String s) {
82             Optional<CENPressEvent> event = Arrays.stream(values()).filter(val -> s.equals(val.press)).findFirst();
83             return event.orElse(null);
84         }
85
86         @Override
87         public String toString() {
88             return press;
89         }
90     }
91
92     private enum CENPlusPressEvent implements PressEvent {
93         CENPLUS_EVENT_SHORT_PRESS("SHORT_PRESS"),
94         CENPLUS_EVENT_START_EXTENDED_PRESS("START_EXTENDED_PRESS"),
95         CENPLUS_EVENT_EXTENDED_PRESS("EXTENDED_PRESS"),
96         CENPLUS_EVENT_RELEASE_EXTENDED_PRESS("RELEASE_EXTENDED_PRESS");
97
98         private final String press;
99
100         CENPlusPressEvent(final String pr) {
101             this.press = pr;
102         }
103
104         public static @Nullable CENPlusPressEvent fromValue(String s) {
105             Optional<CENPlusPressEvent> event = Arrays.stream(values()).filter(val -> s.equals(val.press)).findFirst();
106             return event.orElse(null);
107         }
108
109         @Override
110         public String toString() {
111             return press;
112         }
113     }
114
115     private boolean isCENPlus = false;
116
117     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.SCENARIO_SUPPORTED_THING_TYPES;
118
119     public OpenWebNetScenarioHandler(Thing thing) {
120         super(thing);
121         if (OpenWebNetBindingConstants.THING_TYPE_BUS_CENPLUS_SCENARIO_CONTROL.equals(thing.getThingTypeUID())) {
122             isCENPlus = true;
123             logger.debug("created CEN+ device for thing: {}", getThing().getUID());
124         } else {
125             logger.debug("created CEN device for thing: {}", getThing().getUID());
126         }
127     }
128
129     @Override
130     public void initialize() {
131         super.initialize();
132         Object buttonsConfig = getConfig().get(CONFIG_PROPERTY_SCENARIO_BUTTONS);
133         if (buttonsConfig != null) {
134             Set<Integer> buttons = csvStringToSetInt((String) buttonsConfig);
135             if (!buttons.isEmpty()) {
136                 ThingBuilder thingBuilder = editThing();
137                 Channel ch;
138                 for (Integer i : buttons) {
139                     ch = thing.getChannel(CHANNEL_SCENARIO_BUTTON + i);
140                     if (ch == null) {
141                         thingBuilder.withChannel(buttonToChannel(i));
142                         logger.debug("added channel {} to thing: {}", i, getThing().getUID());
143                     }
144                 }
145                 updateThing(thingBuilder.build());
146             } else {
147                 logger.warn("invalid config parameter buttons='{}' for thing {}", buttonsConfig, thing.getUID());
148             }
149         }
150     }
151
152     @Override
153     public Collection<Class<? extends ThingHandlerService>> getServices() {
154         return Collections.singleton(OpenWebNetCENActions.class);
155     }
156
157     @Override
158     protected String ownIdPrefix() {
159         if (isCENPlus) {
160             return Who.CEN_PLUS_SCENARIO_SCHEDULER.value().toString();
161         } else {
162             return Who.CEN_SCENARIO_SCHEDULER.value().toString();
163         }
164     }
165
166     @Override
167     protected void handleMessage(BaseOpenMessage msg) {
168         super.handleMessage(msg);
169         if (msg.isCommand()) {
170             triggerChannel((CEN) msg);
171         } else {
172             logger.debug("handleMessage() Ignoring unsupported DIM for thing {}. Frame={}", getThing().getUID(), msg);
173         }
174     }
175
176     private void triggerChannel(CEN cenMsg) {
177         Integer buttonNumber;
178         try {
179             buttonNumber = cenMsg.getButtonNumber();
180         } catch (FrameException e) {
181             logger.warn("cannot read CEN/CEN+ button. Ignoring message {}", cenMsg);
182             return;
183         }
184         if (buttonNumber == null || buttonNumber < 0 || buttonNumber > 31) {
185             logger.warn("invalid CEN/CEN+ button number: {}. Ignoring message {}", buttonNumber, cenMsg);
186             return;
187         }
188         Channel ch = thing.getChannel(CHANNEL_SCENARIO_BUTTON + buttonNumber);
189         if (ch == null) { // we have found a new button for this device, let's add a new channel for the button
190             ThingBuilder thingBuilder = editThing();
191             ch = buttonToChannel(buttonNumber);
192             thingBuilder.withChannel(ch);
193             updateThing(thingBuilder.build());
194             logger.info("added new channel {} to thing {}", ch.getUID(), getThing().getUID());
195         }
196         final Channel channel = ch;
197         PressEvent pressEv = null;
198         Pressure press = null;
199         try {
200             press = cenMsg.getButtonPressure();
201         } catch (FrameException e) {
202             logger.warn("invalid CEN/CEN+ Press. Ignoring message {}", cenMsg);
203             return;
204         }
205         if (press == null) {
206             logger.warn("invalid CEN/CEN+ Press. Ignoring message {}", cenMsg);
207             return;
208         }
209
210         if (cenMsg instanceof CENScenario) {
211             switch ((CENPressure) press) {
212                 case START_PRESSURE:
213                     pressEv = CENPressEvent.CEN_EVENT_START_PRESS;
214                     break;
215                 case RELEASE_SHORT_PRESSURE:
216                     pressEv = CENPressEvent.CEN_EVENT_SHORT_PRESS;
217                     break;
218                 case EXTENDED_PRESSURE:
219                     pressEv = CENPressEvent.CEN_EVENT_EXTENDED_PRESS;
220                     break;
221                 case RELEASE_EXTENDED_PRESSURE:
222                     pressEv = CENPressEvent.CEN_EVENT_RELEASE_EXTENDED_PRESS;
223                     break;
224                 default:
225                     logger.warn("unsupported CENPress. Ignoring message {}", cenMsg);
226                     return;
227             }
228         } else {
229             switch ((CENPlusPressure) press) {
230                 case SHORT_PRESSURE:
231                     pressEv = CENPlusPressEvent.CENPLUS_EVENT_SHORT_PRESS;
232                     break;
233                 case START_EXTENDED_PRESSURE:
234                     pressEv = CENPlusPressEvent.CENPLUS_EVENT_START_EXTENDED_PRESS;
235                     break;
236                 case EXTENDED_PRESSURE:
237                     pressEv = CENPlusPressEvent.CENPLUS_EVENT_EXTENDED_PRESS;
238                     break;
239                 case RELEASE_EXTENDED_PRESSURE:
240                     pressEv = CENPlusPressEvent.CENPLUS_EVENT_RELEASE_EXTENDED_PRESS;
241                     break;
242                 default:
243                     logger.warn("unsupported CENPlusPress. Ignoring message {}", cenMsg);
244                     return;
245             }
246         }
247
248         triggerChannel(channel.getUID(), pressEv.toString());
249     }
250
251     private Channel buttonToChannel(int buttonNumber) {
252         ChannelTypeUID channelTypeUID;
253         if (isCENPlus) {
254             channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_CEN_PLUS_BUTTON_EVENT);
255         } else {
256             channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_CEN_BUTTON_EVENT);
257         }
258         return ChannelBuilder
259                 .create(new ChannelUID(getThing().getUID(), CHANNEL_SCENARIO_BUTTON + buttonNumber), "String")
260                 .withType(channelTypeUID).withKind(ChannelKind.TRIGGER).withLabel("Button " + buttonNumber).build();
261     }
262
263     /**
264      * Construct a CEN/CEN+ virtual press message for this device given a pressString and button number
265      *
266      * @param pressString one START_PRESS, SHORT_PRESS etc.
267      * @param button number [0-31]
268      * @return CEN message
269      * @throws IllegalArgumentException if button number or pressString are invalid
270      */
271     public CEN pressStrToMessage(String pressString, int button) throws IllegalArgumentException {
272         Where w = deviceWhere;
273         if (w == null) {
274             throw new IllegalArgumentException("pressStrToMessage: deviceWhere is null");
275         }
276         if (isCENPlus) {
277             CENPlusPressEvent prEvent = CENPlusPressEvent.fromValue(pressString);
278             if (prEvent != null) {
279                 switch (prEvent) {
280                     case CENPLUS_EVENT_SHORT_PRESS:
281                         return CENPlusScenario.virtualShortPressure(w.value(), button);
282                     case CENPLUS_EVENT_START_EXTENDED_PRESS:
283                         return CENPlusScenario.virtualStartExtendedPressure(w.value(), button);
284                     case CENPLUS_EVENT_EXTENDED_PRESS:
285                         return CENPlusScenario.virtualExtendedPressure(w.value(), button);
286                     case CENPLUS_EVENT_RELEASE_EXTENDED_PRESS:
287                         return CENPlusScenario.virtualReleaseExtendedPressure(w.value(), button);
288                     default:
289                         throw new IllegalArgumentException("unsupported press type: " + pressString);
290                 }
291             } else {
292                 throw new IllegalArgumentException("unsupported press type: " + pressString);
293             }
294         } else {
295             CENPressEvent prEvent = CENPressEvent.fromValue(pressString);
296             if (prEvent != null) {
297                 switch (prEvent) {
298                     case CEN_EVENT_START_PRESS:
299                         return CENScenario.virtualStartPressure(w.value(), button);
300                     case CEN_EVENT_SHORT_PRESS:
301                         return CENScenario.virtualReleaseShortPressure(w.value(), button);
302                     case CEN_EVENT_EXTENDED_PRESS:
303                         return CENScenario.virtualExtendedPressure(w.value(), button);
304                     case CEN_EVENT_RELEASE_EXTENDED_PRESS:
305                         return CENScenario.virtualReleaseExtendedPressure(w.value(), button);
306                     default:
307                         throw new IllegalArgumentException("unsupported press type: " + pressString);
308                 }
309             } else {
310                 throw new IllegalArgumentException("unsupported press type: " + pressString);
311             }
312         }
313     }
314
315     private static Set<Integer> csvStringToSetInt(String s) {
316         TreeSet<Integer> intSet = new TreeSet<Integer>();
317         String sNorm = s.replaceAll("\\s", "");
318         Scanner sc = new Scanner(sNorm);
319         sc.useDelimiter(",");
320         while (sc.hasNextInt()) {
321             intSet.add(sc.nextInt());
322         }
323         sc.close();
324         return intSet;
325     }
326
327     @Override
328     protected void handleChannelCommand(ChannelUID channel, Command command) {
329         logger.warn("CEN/CEN+ channels are trigger channels and do not handle commands");
330     }
331
332     @Override
333     protected void refreshDevice(boolean refreshAll) {
334         logger.debug("CEN/CEN+ channels are trigger channels and do not have state");
335     }
336
337     @Override
338     protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
339         return new WhereCEN(wStr);
340     }
341
342     @Override
343     protected void requestChannelState(ChannelUID channel) {
344         logger.debug("CEN/CEN+ channels are trigger channels and do not have state");
345     }
346 }