]> git.basschouten.com Git - openhab-addons.git/blob
1a112c33107d586f3e62f3025d991ec2d568929e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.ihc.internal.profiles;
14
15 import java.math.BigDecimal;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
18
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;
37
38 /**
39  * The {@link PushButtonToCommandProfile} transforms push button channel events to commands.
40  *
41  * @author Pauli Anttila - Initial contribution
42  */
43 @NonNullByDefault
44 public class PushButtonToCommandProfile implements TriggerProfile {
45
46     private final Logger logger = LoggerFactory.getLogger(PushButtonToCommandProfile.class);
47
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";
53
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;
59
60     private final ProfileCallback callback;
61
62     private final ProfileContext context;
63
64     @Nullable
65     private Command shortPressCommand;
66
67     @Nullable
68     private Command longPressCommand;
69
70     @Nullable
71     private ScheduledFuture<?> dimmFuture;
72     @Nullable
73     private ScheduledFuture<?> timeoutFuture;
74
75     @Nullable
76     private State previousState;
77
78     private long pressedTime = 0;
79
80     private long longPressTime;
81     private long repeatTime;
82     private long timeout;
83
84     PushButtonToCommandProfile(ProfileCallback callback, ProfileContext context) {
85         this.callback = callback;
86         this.context = context;
87
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);
93     }
94
95     private long getParamAsLong(String param, long defValue) {
96         long retval;
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();
101         } else {
102             logger.debug("Parameter '{}' is not of type BigDecimal, using default value '{}'", param, defValue);
103             retval = defValue;
104         }
105         return retval;
106     }
107
108     private @Nullable Command getParamAsCommand(String param, Command defValue) {
109         Command retval;
110         Object paramValue = context.getConfiguration().get(param);
111         logger.debug("Configuring profile with {} parameter '{}'", param, paramValue);
112
113         if (paramValue instanceof String) {
114             try {
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);
118                 retval = defValue;
119             }
120
121         } else {
122             logger.debug("Parameter '{}' is not of type String, using default value '{}'", param, defValue);
123             retval = defValue;
124         }
125
126         return retval;
127     }
128
129     private @Nullable Command convertStringToCommand(String str) throws IllegalArgumentException {
130         Command retval = null;
131         switch (str) {
132             case "ON":
133                 retval = OnOffType.ON;
134                 break;
135             case "OFF":
136                 retval = OnOffType.OFF;
137                 break;
138             case "STOP":
139                 retval = StopMoveType.STOP;
140                 break;
141             case "MOVE":
142                 retval = StopMoveType.MOVE;
143                 break;
144             case "PLAY":
145                 retval = PlayPauseType.PLAY;
146                 break;
147             case "PAUSE":
148                 retval = PlayPauseType.PAUSE;
149                 break;
150             case "NEXT":
151                 retval = NextPreviousType.NEXT;
152                 break;
153             case "PREVIOUS":
154                 retval = NextPreviousType.PREVIOUS;
155                 break;
156             case "FASTFORWARD":
157                 retval = RewindFastforwardType.FASTFORWARD;
158                 break;
159             case "REWIND":
160                 retval = RewindFastforwardType.REWIND;
161                 break;
162             case "INCREASE":
163                 retval = IncreaseDecreaseType.INCREASE;
164                 break;
165             case "DECREASE":
166                 retval = IncreaseDecreaseType.DECREASE;
167                 break;
168             case "UP":
169                 retval = UpDownType.UP;
170                 break;
171             case "DOWN":
172                 retval = UpDownType.DOWN;
173                 break;
174             case "TOGGLE":
175                 break; // return null
176
177             default:
178                 throw new IllegalArgumentException("Illegal argument '" + str + "'");
179         }
180         return retval;
181     }
182
183     @Override
184     public ProfileTypeUID getProfileTypeUID() {
185         return IhcProfiles.PUSHBUTTON_COMMAND;
186     }
187
188     @Override
189     public void onStateUpdateFromItem(State state) {
190         previousState = state;
191     }
192
193     @Override
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));
199         }
200     }
201
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));
206         }
207         return command;
208     }
209
210     private synchronized void buttonPressed(Command commandToSend) {
211         cancelTimeoutFuture();
212         cancelDimmFuture();
213
214         if (repeatTime > 0) {
215             // run repeatedly
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);
220         } else {
221             // run only ones
222             dimmFuture = context.getExecutorService().schedule(() -> callback.sendCommand(commandToSend),
223                     longPressTime + 50, TimeUnit.MILLISECONDS);
224         }
225
226         pressedTime = System.currentTimeMillis();
227     }
228
229     private synchronized void buttonReleased(Command commandToSend) {
230         cancelTimeoutFuture();
231         cancelDimmFuture();
232
233         if (System.currentTimeMillis() - pressedTime <= longPressTime) {
234             callback.sendCommand(commandToSend);
235         }
236     }
237
238     private synchronized void cancelDimmFuture() {
239         if (dimmFuture != null) {
240             dimmFuture.cancel(false);
241         }
242     }
243
244     private synchronized void cancelTimeoutFuture() {
245         if (timeoutFuture != null) {
246             timeoutFuture.cancel(false);
247         }
248     }
249 }