]> git.basschouten.com Git - openhab-addons.git/blob
c99e9669ab3ae45b6e8fe6a71903115366158de0
[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.lcn.internal;
14
15 import java.nio.ByteBuffer;
16 import java.util.Arrays;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.lcn.internal.common.LcnChannelGroup;
21 import org.openhab.binding.lcn.internal.common.LcnDefs;
22 import org.openhab.binding.lcn.internal.common.LcnDefs.KeyTable;
23 import org.openhab.binding.lcn.internal.common.LcnDefs.SendKeyCommand;
24 import org.openhab.binding.lcn.internal.common.LcnException;
25 import org.openhab.binding.lcn.internal.common.PckGenerator;
26 import org.openhab.core.automation.annotation.ActionInput;
27 import org.openhab.core.automation.annotation.RuleAction;
28 import org.openhab.core.thing.binding.ThingActions;
29 import org.openhab.core.thing.binding.ThingActionsScope;
30 import org.openhab.core.thing.binding.ThingHandler;
31 import org.osgi.service.component.annotations.Component;
32 import org.osgi.service.component.annotations.ServiceScope;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * Handles actions requested to be sent to an LCN module.
38  *
39  * @author Fabian Wolter - Initial contribution
40  */
41 @Component(scope = ServiceScope.PROTOTYPE, service = LcnModuleActions.class)
42 @ThingActionsScope(name = "lcn")
43 @NonNullByDefault
44 public class LcnModuleActions implements ThingActions {
45     private final Logger logger = LoggerFactory.getLogger(LcnModuleActions.class);
46     private static final int MAX_BEEP_VOLUME = 100;
47     private static final int MAX_BEEP_COUNT = 50;
48     private static final int DYN_TEXT_CHUNK_COUNT = 5;
49     private static final int DYN_TEXT_HEADER_LENGTH = 6;
50     private static final int DYN_TEXT_CHUNK_LENGTH = 12;
51     private @Nullable LcnModuleHandler moduleHandler;
52
53     @Override
54     public void setThingHandler(@Nullable ThingHandler handler) {
55         this.moduleHandler = (LcnModuleHandler) handler;
56     }
57
58     @Override
59     public @Nullable ThingHandler getThingHandler() {
60         return moduleHandler;
61     }
62
63     @RuleAction(label = "send a hit key command", description = "Sends a \"hit key\" command to an LCN module.")
64     public void hitKey(
65             @ActionInput(name = "table", required = true, type = "java.lang.String", label = "Table", description = "The key table (A-D)") @Nullable String table,
66             @ActionInput(name = "key", required = true, type = "java.lang.Integer", label = "Key", description = "The key number (1-8)") int key,
67             @ActionInput(name = "action", required = true, type = "java.lang.String", label = "Action", description = "The action (HIT, MAKE, BREAK)") @Nullable String action) {
68         try {
69             if (table == null) {
70                 throw new LcnException("Table is not set");
71             }
72
73             if (action == null) {
74                 throw new LcnException("Action is not set");
75             }
76
77             KeyTable keyTable;
78             try {
79                 keyTable = LcnDefs.KeyTable.valueOf(table.toUpperCase());
80             } catch (IllegalArgumentException e) {
81                 throw new LcnException("Unknown key table: " + table);
82             }
83
84             SendKeyCommand sendKeyCommand;
85             try {
86                 sendKeyCommand = SendKeyCommand.valueOf(action.toUpperCase());
87             } catch (IllegalArgumentException e) {
88                 throw new LcnException("Unknown action: " + action);
89             }
90
91             if (!LcnChannelGroup.KEYLOCKTABLEA.isValidId(key - 1)) {
92                 throw new LcnException("Key number is out of range: " + key);
93             }
94
95             SendKeyCommand[] cmds = new SendKeyCommand[LcnDefs.KEY_TABLE_COUNT];
96             Arrays.fill(cmds, SendKeyCommand.DONTSEND);
97             boolean[] keys = new boolean[LcnChannelGroup.KEYLOCKTABLEA.getCount()];
98
99             int keyTableNumber = keyTable.name().charAt(0) - LcnDefs.KeyTable.A.name().charAt(0);
100             cmds[keyTableNumber] = sendKeyCommand;
101             keys[key - 1] = true;
102
103             getHandler().sendPck(PckGenerator.sendKeys(cmds, keys));
104         } catch (LcnException e) {
105             logger.warn("Could not execute hit key command: {}", e.getMessage());
106         }
107     }
108
109     @RuleAction(label = "flicker a dimmer output", description = "Let a dimmer output flicker for a given count of flashes.")
110     public void flickerOutput(
111             @ActionInput(name = "output", type = "java.lang.Integer", required = true, label = "Output", description = "The output number (1-4)") int output,
112             @ActionInput(name = "depth", type = "java.lang.Integer", label = "Depth", description = "0=25% 1=50% 2=100%") int depth,
113             @ActionInput(name = "ramp", type = "java.lang.Integer", label = "Ramp", description = "0=2sec 1=1sec 2=0.5sec") int ramp,
114             @ActionInput(name = "count", type = "java.lang.Integer", label = "Count", description = "Number of flashes (1-15)") int count) {
115         try {
116             getHandler().sendPck(PckGenerator.flickerOutput(output - 1, depth, ramp, count));
117         } catch (LcnException e) {
118             logger.warn("Could not send output flicker command: {}", e.getMessage());
119         }
120     }
121
122     @RuleAction(label = "send a custom text", description = "Send custom text to an LCN-GTxD display.")
123     public void sendDynamicText(
124             @ActionInput(name = "row", type = "java.lang.Integer", required = true, label = "Row", description = "Display the text on the LCN-GTxD in the given row number (1-4)") int row,
125             @ActionInput(name = "text", type = "java.lang.String", label = "Text", description = "The text to display (max. 60 chars/bytes)") @Nullable String textInput) {
126         try {
127             String text = textInput;
128
129             if (text == null) {
130                 text = new String();
131             }
132
133             // some LCN-GTxD don't display the text if it fits exactly in one chunk. Observed with GT10D 8.0.
134             if (text.getBytes(LcnDefs.LCN_ENCODING).length % DYN_TEXT_CHUNK_LENGTH == 0) {
135                 text += " ";
136             }
137
138             // convert String to bytes to split the data every 12 bytes, because a unicode character can take more than
139             // one byte
140             ByteBuffer bb = ByteBuffer.wrap(text.getBytes(LcnDefs.LCN_ENCODING));
141
142             if (bb.capacity() > DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT) {
143                 logger.warn("Dynamic text truncated. Has {} bytes: '{}'", bb.capacity(), text);
144             }
145
146             bb.limit(Math.min(DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT, bb.capacity()));
147
148             int part = 0;
149             while (bb.hasRemaining()) {
150                 byte[] chunk = new byte[DYN_TEXT_CHUNK_LENGTH];
151                 bb.get(chunk, 0, Math.min(bb.remaining(), DYN_TEXT_CHUNK_LENGTH));
152
153                 ByteBuffer command = ByteBuffer.allocate(DYN_TEXT_HEADER_LENGTH + DYN_TEXT_CHUNK_LENGTH);
154                 command.put(PckGenerator.dynTextHeader(row - 1, part++).getBytes(LcnDefs.LCN_ENCODING));
155                 command.put(chunk);
156
157                 getHandler().sendPck(command.array());
158             }
159         } catch (IllegalArgumentException | LcnException e) {
160             logger.warn("Could not send dynamic text: {}", e.getMessage());
161         }
162     }
163
164     /**
165      * Start an lcn relay timer with the given duration [ms]
166      *
167      * @param relayNumber 1-based number of the relay to use
168      * @param duration duration of the relay timer in milliseconds
169      */
170     @RuleAction(label = "start a relay timer", description = "Start an LCN relay timer.")
171     public void startRelayTimer(
172             @ActionInput(name = "relaynumber", required = true, type = "java.lang.Integer", label = "Relay Number", description = "The relay number (1-8)") int relayNumber,
173             @ActionInput(name = "duration", required = true, type = "java.lang.Double", label = "Duration [ms]", description = "The timer duration in milliseconds") double duration) {
174         try {
175             getHandler().sendPck(PckGenerator.startRelayTimer(relayNumber, duration));
176         } catch (LcnException e) {
177             logger.warn("Could not send start relay timer command: {}", e.getMessage());
178         }
179     }
180
181     /**
182      * Let the beeper connected to the LCN module beep.
183      *
184      * @param soundVolume sound volume in percent. Can be null. Then, the last volume is used.
185      * @param tonality N=normal, S=special, 1-7 tonalities 1-7. Can be null. Then, normal tonality is used.
186      * @param count number of beeps. Can be null. Then, number of beeps is one.
187      */
188     @RuleAction(label = "let the module's beeper beep", description = "Lets the beeper connected to the LCN module beep")
189     public void beep(
190             @ActionInput(name = "volume", required = false, type = "java.lang.Double", label = "Sound Volume", description = "The sound volume in percent.") @Nullable Double soundVolume,
191             @ActionInput(name = "tonality", required = false, type = "java.lang.String", label = "Tonality", description = "Tonality (N, S, 1-7)") @Nullable String tonality,
192             @ActionInput(name = "count", required = false, type = "java.lang.Integer", label = "Count", description = "Number of beeps") @Nullable Integer count) {
193         try {
194             if (soundVolume != null) {
195                 if (soundVolume < 0) {
196                     throw new LcnException("Volume cannot be negative: " + soundVolume);
197                 }
198                 getHandler().sendPck(PckGenerator.setBeepVolume(Math.min(soundVolume, MAX_BEEP_VOLUME)));
199             }
200
201             Integer localCount = count;
202             if (localCount == null) {
203                 localCount = 1;
204             }
205
206             String filteredTonality = LcnBindingConstants.ALLOWED_BEEP_TONALITIES.stream() //
207                     .filter(t -> t.equals(tonality)) //
208                     .findAny() //
209                     .orElse("N");
210
211             getHandler().sendPck(PckGenerator.beep(filteredTonality, Math.min(localCount, MAX_BEEP_COUNT)));
212         } catch (LcnException e) {
213             logger.warn("Could not send beep command: {}", e.getMessage());
214         }
215     }
216
217     /** Static alias to support the old DSL rules engine and make the action available there. */
218     public static void hitKey(ThingActions actions, @Nullable String table, int key, @Nullable String action) {
219         ((LcnModuleActions) actions).hitKey(table, key, action);
220     }
221
222     /** Static alias to support the old DSL rules engine and make the action available there. */
223     public static void flickerOutput(ThingActions actions, int output, int depth, int ramp, int count) {
224         ((LcnModuleActions) actions).flickerOutput(output, depth, ramp, count);
225     }
226
227     /** Static alias to support the old DSL rules engine and make the action available there. */
228     public static void sendDynamicText(ThingActions actions, int row, @Nullable String text) {
229         ((LcnModuleActions) actions).sendDynamicText(row, text);
230     }
231
232     /** Static alias to support the old DSL rules engine and make the action available there. */
233     public static void startRelayTimer(ThingActions actions, int relaynumber, double duration) {
234         ((LcnModuleActions) actions).startRelayTimer(relaynumber, duration);
235     }
236
237     /** Static alias to support the old DSL rules engine and make the action available there. */
238     public static void beep(ThingActions actions, Double soundVolume, String tonality, Integer count) {
239         ((LcnModuleActions) actions).beep(soundVolume, tonality, count);
240     }
241
242     private LcnModuleHandler getHandler() throws LcnException {
243         LcnModuleHandler localModuleHandler = moduleHandler;
244         if (localModuleHandler != null) {
245             return localModuleHandler;
246         } else {
247             throw new LcnException("Handler not set");
248         }
249     }
250 }