2 * Copyright (c) 2010-2024 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.ihc.internal.profiles;
15 import java.math.BigDecimal;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.ihc.internal.IhcBindingConstants;
22 import org.openhab.core.library.types.IncreaseDecreaseType;
23 import org.openhab.core.library.types.NextPreviousType;
24 import org.openhab.core.library.types.OnOffType;
25 import org.openhab.core.library.types.PlayPauseType;
26 import org.openhab.core.library.types.RewindFastforwardType;
27 import org.openhab.core.library.types.StopMoveType;
28 import org.openhab.core.library.types.UpDownType;
29 import org.openhab.core.thing.profiles.ProfileCallback;
30 import org.openhab.core.thing.profiles.ProfileContext;
31 import org.openhab.core.thing.profiles.ProfileTypeUID;
32 import org.openhab.core.thing.profiles.TriggerProfile;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.State;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * The {@link PushButtonToCommandProfile} transforms push button channel events to commands.
41 * @author Pauli Anttila - Initial contribution
44 public class PushButtonToCommandProfile implements TriggerProfile {
46 private final Logger logger = LoggerFactory.getLogger(PushButtonToCommandProfile.class);
48 private static final String PARAM_SHORT_PRESS_COMMAND = "short-press-command";
49 private static final String PARAM_LONG_PRESS_COMMAND = "long-press-command";
50 private static final String PARAM_LONG_PRESS_TIME = "long-press-time";
51 private static final String PARAM_REPEAT_TIME = "repeat-time";
52 private static final String PARAM_TIMEOUT_TIME = "timeout";
54 private static final Command DEF_SHORT_PRESS_COMMAND = OnOffType.ON;
55 private static final Command DEF_LONG_PRESS_COMMAND = IncreaseDecreaseType.INCREASE;
56 private static final long DEF_LONG_PRESS_TIME = 1000;
57 private static final long DEF_REPEAT_TIME = 200;
58 private static final long DEF_TIMEOUT_TIME = 10000;
60 private final ProfileCallback callback;
62 private final ProfileContext context;
65 private Command shortPressCommand;
68 private Command longPressCommand;
71 private ScheduledFuture<?> dimmFuture;
73 private ScheduledFuture<?> timeoutFuture;
76 private State previousState;
78 private long pressedTime = 0;
80 private long longPressTime;
81 private long repeatTime;
84 PushButtonToCommandProfile(ProfileCallback callback, ProfileContext context) {
85 this.callback = callback;
86 this.context = context;
88 shortPressCommand = getParamAsCommand(PARAM_SHORT_PRESS_COMMAND, DEF_SHORT_PRESS_COMMAND);
89 longPressCommand = getParamAsCommand(PARAM_LONG_PRESS_COMMAND, DEF_LONG_PRESS_COMMAND);
90 longPressTime = getParamAsLong(PARAM_LONG_PRESS_TIME, DEF_LONG_PRESS_TIME);
91 repeatTime = getParamAsLong(PARAM_REPEAT_TIME, DEF_REPEAT_TIME);
92 timeout = getParamAsLong(PARAM_TIMEOUT_TIME, DEF_TIMEOUT_TIME);
95 private long getParamAsLong(String param, long defValue) {
97 Object paramValue = context.getConfiguration().get(param);
98 logger.debug("Configuring profile with {} parameter '{}'", param, paramValue);
99 if (paramValue instanceof BigDecimal decimalParam) {
100 retval = decimalParam.longValue();
102 logger.debug("Parameter '{}' is not of type BigDecimal, using default value '{}'", param, defValue);
108 private @Nullable Command getParamAsCommand(String param, Command defValue) {
110 Object paramValue = context.getConfiguration().get(param);
111 logger.debug("Configuring profile with {} parameter '{}'", param, paramValue);
113 if (paramValue instanceof String) {
115 retval = convertStringToCommand(String.valueOf(paramValue));
116 } catch (IllegalArgumentException e) {
117 logger.warn("Parameter '{}' is not a valid command type, using default value '{}'", param, defValue);
122 logger.debug("Parameter '{}' is not of type String, using default value '{}'", param, defValue);
129 private @Nullable Command convertStringToCommand(String str) throws IllegalArgumentException {
130 Command retval = null;
133 retval = OnOffType.ON;
136 retval = OnOffType.OFF;
139 retval = StopMoveType.STOP;
142 retval = StopMoveType.MOVE;
145 retval = PlayPauseType.PLAY;
148 retval = PlayPauseType.PAUSE;
151 retval = NextPreviousType.NEXT;
154 retval = NextPreviousType.PREVIOUS;
157 retval = RewindFastforwardType.FASTFORWARD;
160 retval = RewindFastforwardType.REWIND;
163 retval = IncreaseDecreaseType.INCREASE;
166 retval = IncreaseDecreaseType.DECREASE;
169 retval = UpDownType.UP;
172 retval = UpDownType.DOWN;
175 break; // return null
178 throw new IllegalArgumentException("Illegal argument '" + str + "'");
184 public ProfileTypeUID getProfileTypeUID() {
185 return IhcProfiles.PUSHBUTTON_COMMAND;
189 public void onStateUpdateFromItem(State state) {
190 previousState = state;
194 public void onTriggerFromHandler(String event) {
195 if (IhcBindingConstants.EVENT_PRESSED.equals(event)) {
196 buttonPressed(toggleCommandIfNeeded(longPressCommand));
197 } else if (IhcBindingConstants.EVENT_RELEASED.equals(event)) {
198 buttonReleased(toggleCommandIfNeeded(shortPressCommand));
202 private Command toggleCommandIfNeeded(@Nullable Command command) {
203 if (command == null) {
204 logger.debug("Toggling command: previous state is '{}'", previousState);
205 return OnOffType.from(!OnOffType.ON.equals(previousState));
210 private synchronized void buttonPressed(Command commandToSend) {
211 cancelTimeoutFuture();
214 if (repeatTime > 0) {
216 dimmFuture = context.getExecutorService().scheduleWithFixedDelay(() -> callback.sendCommand(commandToSend),
217 longPressTime + 50, repeatTime, TimeUnit.MILLISECONDS);
218 timeoutFuture = context.getExecutorService().schedule(() -> this.cancelDimmFuture(), timeout,
219 TimeUnit.MILLISECONDS);
222 dimmFuture = context.getExecutorService().schedule(() -> callback.sendCommand(commandToSend),
223 longPressTime + 50, TimeUnit.MILLISECONDS);
226 pressedTime = System.currentTimeMillis();
229 private synchronized void buttonReleased(Command commandToSend) {
230 cancelTimeoutFuture();
233 if (System.currentTimeMillis() - pressedTime <= longPressTime) {
234 callback.sendCommand(commandToSend);
238 private synchronized void cancelDimmFuture() {
239 if (dimmFuture != null) {
240 dimmFuture.cancel(false);
244 private synchronized void cancelTimeoutFuture() {
245 if (timeoutFuture != null) {
246 timeoutFuture.cancel(false);