2 * Copyright (c) 2010-2021 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.openwebnet.internal.handler;
15 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
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;
23 import java.util.TreeSet;
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;
54 * The {@link OpenWebNetScenarioHandler} is responsible for handling commands/messages for CEN/CEN+ Scenarios. It
55 * extends the abstract {@link OpenWebNetThingHandler}.
57 * @author Massimo Valla - Initial contribution
60 public class OpenWebNetScenarioHandler extends OpenWebNetThingHandler {
62 private final Logger logger = LoggerFactory.getLogger(OpenWebNetScenarioHandler.class);
64 private interface PressEvent {
66 public String toString();
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");
75 private final String press;
77 CENPressEvent(final String pr) {
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);
87 public String toString() {
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");
98 private final String press;
100 CENPlusPressEvent(final String pr) {
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);
110 public String toString() {
115 private boolean isCENPlus = false;
117 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.SCENARIO_SUPPORTED_THING_TYPES;
119 public OpenWebNetScenarioHandler(Thing thing) {
121 if (OpenWebNetBindingConstants.THING_TYPE_BUS_CENPLUS_SCENARIO_CONTROL.equals(thing.getThingTypeUID())) {
123 logger.debug("created CEN+ device for thing: {}", getThing().getUID());
125 logger.debug("created CEN device for thing: {}", getThing().getUID());
130 public void 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();
138 for (Integer i : buttons) {
139 ch = thing.getChannel(CHANNEL_SCENARIO_BUTTON + i);
141 thingBuilder.withChannel(buttonToChannel(i));
142 logger.debug("added channel {} to thing: {}", i, getThing().getUID());
145 updateThing(thingBuilder.build());
147 logger.warn("invalid config parameter buttons='{}' for thing {}", buttonsConfig, thing.getUID());
153 public Collection<Class<? extends ThingHandlerService>> getServices() {
154 return Collections.singleton(OpenWebNetCENActions.class);
158 protected String ownIdPrefix() {
160 return Who.CEN_PLUS_SCENARIO_SCHEDULER.value().toString();
162 return Who.CEN_SCENARIO_SCHEDULER.value().toString();
167 protected void handleMessage(BaseOpenMessage msg) {
168 super.handleMessage(msg);
169 if (msg.isCommand()) {
170 triggerChannel((CEN) msg);
172 logger.debug("handleMessage() Ignoring unsupported DIM for thing {}. Frame={}", getThing().getUID(), msg);
176 private void triggerChannel(CEN cenMsg) {
177 Integer buttonNumber;
179 buttonNumber = cenMsg.getButtonNumber();
180 } catch (FrameException e) {
181 logger.warn("cannot read CEN/CEN+ button. Ignoring message {}", cenMsg);
184 if (buttonNumber == null || buttonNumber < 0 || buttonNumber > 31) {
185 logger.warn("invalid CEN/CEN+ button number: {}. Ignoring message {}", buttonNumber, cenMsg);
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());
196 final Channel channel = ch;
197 PressEvent pressEv = null;
198 Pressure press = null;
200 press = cenMsg.getButtonPressure();
201 } catch (FrameException e) {
202 logger.warn("invalid CEN/CEN+ Press. Ignoring message {}", cenMsg);
206 logger.warn("invalid CEN/CEN+ Press. Ignoring message {}", cenMsg);
210 if (cenMsg instanceof CENScenario) {
211 switch ((CENPressure) press) {
213 pressEv = CENPressEvent.CEN_EVENT_START_PRESS;
215 case RELEASE_SHORT_PRESSURE:
216 pressEv = CENPressEvent.CEN_EVENT_SHORT_PRESS;
218 case EXTENDED_PRESSURE:
219 pressEv = CENPressEvent.CEN_EVENT_EXTENDED_PRESS;
221 case RELEASE_EXTENDED_PRESSURE:
222 pressEv = CENPressEvent.CEN_EVENT_RELEASE_EXTENDED_PRESS;
225 logger.warn("unsupported CENPress. Ignoring message {}", cenMsg);
229 switch ((CENPlusPressure) press) {
231 pressEv = CENPlusPressEvent.CENPLUS_EVENT_SHORT_PRESS;
233 case START_EXTENDED_PRESSURE:
234 pressEv = CENPlusPressEvent.CENPLUS_EVENT_START_EXTENDED_PRESS;
236 case EXTENDED_PRESSURE:
237 pressEv = CENPlusPressEvent.CENPLUS_EVENT_EXTENDED_PRESS;
239 case RELEASE_EXTENDED_PRESSURE:
240 pressEv = CENPlusPressEvent.CENPLUS_EVENT_RELEASE_EXTENDED_PRESS;
243 logger.warn("unsupported CENPlusPress. Ignoring message {}", cenMsg);
248 triggerChannel(channel.getUID(), pressEv.toString());
251 private Channel buttonToChannel(int buttonNumber) {
252 ChannelTypeUID channelTypeUID;
254 channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_CEN_PLUS_BUTTON_EVENT);
256 channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_CEN_BUTTON_EVENT);
258 return ChannelBuilder
259 .create(new ChannelUID(getThing().getUID(), CHANNEL_SCENARIO_BUTTON + buttonNumber), "String")
260 .withType(channelTypeUID).withKind(ChannelKind.TRIGGER).withLabel("Button " + buttonNumber).build();
264 * Construct a CEN/CEN+ virtual press message for this device given a pressString and button number
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
271 public CEN pressStrToMessage(String pressString, int button) throws IllegalArgumentException {
272 Where w = deviceWhere;
274 throw new IllegalArgumentException("pressStrToMessage: deviceWhere is null");
277 CENPlusPressEvent prEvent = CENPlusPressEvent.fromValue(pressString);
278 if (prEvent != null) {
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);
289 throw new IllegalArgumentException("unsupported press type: " + pressString);
292 throw new IllegalArgumentException("unsupported press type: " + pressString);
295 CENPressEvent prEvent = CENPressEvent.fromValue(pressString);
296 if (prEvent != null) {
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);
307 throw new IllegalArgumentException("unsupported press type: " + pressString);
310 throw new IllegalArgumentException("unsupported press type: " + pressString);
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());
328 protected void handleChannelCommand(ChannelUID channel, Command command) {
329 logger.warn("CEN/CEN+ channels are trigger channels and do not handle commands");
333 protected void refreshDevice(boolean refreshAll) {
334 logger.debug("CEN/CEN+ channels are trigger channels and do not have state");
338 protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
339 return new WhereCEN(wStr);
343 protected void requestChannelState(ChannelUID channel) {
344 logger.debug("CEN/CEN+ channels are trigger channels and do not have state");