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.lcn.internal;
15 import java.nio.ByteBuffer;
16 import java.util.Arrays;
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.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * Handles actions requested to be sent to an LCN module.
37 * @author Fabian Wolter - Initial contribution
39 @ThingActionsScope(name = "lcn")
41 public class LcnModuleActions implements ThingActions {
42 private final Logger logger = LoggerFactory.getLogger(LcnModuleActions.class);
43 private static final int DYN_TEXT_CHUNK_COUNT = 5;
44 private static final int DYN_TEXT_HEADER_LENGTH = 6;
45 private static final int DYN_TEXT_CHUNK_LENGTH = 12;
46 private @Nullable LcnModuleHandler moduleHandler;
49 public void setThingHandler(@Nullable ThingHandler handler) {
50 this.moduleHandler = (LcnModuleHandler) handler;
54 public @Nullable ThingHandler getThingHandler() {
58 @RuleAction(label = "send a hit key command", description = "Sends a \"hit key\" command to an LCN module.")
60 @ActionInput(name = "table", required = true, type = "java.lang.String", label = "Table", description = "The key table (A-D)") @Nullable String table,
61 @ActionInput(name = "key", required = true, type = "java.lang.Integer", label = "Key", description = "The key number (1-8)") int key,
62 @ActionInput(name = "action", required = true, type = "java.lang.String", label = "Action", description = "The action (HIT, MAKE, BREAK)") @Nullable String action) {
65 throw new LcnException("Table is not set");
69 throw new LcnException("Action is not set");
74 keyTable = LcnDefs.KeyTable.valueOf(table.toUpperCase());
75 } catch (IllegalArgumentException e) {
76 throw new LcnException("Unknown key table: " + table);
79 SendKeyCommand sendKeyCommand;
81 sendKeyCommand = SendKeyCommand.valueOf(action.toUpperCase());
82 } catch (IllegalArgumentException e) {
83 throw new LcnException("Unknown action: " + action);
86 if (!LcnChannelGroup.KEYLOCKTABLEA.isValidId(key - 1)) {
87 throw new LcnException("Key number is out of range: " + key);
90 SendKeyCommand[] cmds = new SendKeyCommand[LcnDefs.KEY_TABLE_COUNT];
91 Arrays.fill(cmds, SendKeyCommand.DONTSEND);
92 boolean[] keys = new boolean[LcnChannelGroup.KEYLOCKTABLEA.getCount()];
94 int keyTableNumber = keyTable.name().charAt(0) - LcnDefs.KeyTable.A.name().charAt(0);
95 cmds[keyTableNumber] = sendKeyCommand;
98 getHandler().sendPck(PckGenerator.sendKeys(cmds, keys));
99 } catch (LcnException e) {
100 logger.warn("Could not execute hit key command: {}", e.getMessage());
104 @RuleAction(label = "flicker a dimmer output", description = "Let a dimmer output flicker for a given count of flashes.")
105 public void flickerOutput(
106 @ActionInput(name = "output", type = "java.lang.Integer", required = true, label = "Output", description = "The output number (1-4)") int output,
107 @ActionInput(name = "depth", type = "java.lang.Integer", label = "Depth", description = "0=25% 1=50% 2=100%") int depth,
108 @ActionInput(name = "ramp", type = "java.lang.Integer", label = "Ramp", description = "0=2sec 1=1sec 2=0.5sec") int ramp,
109 @ActionInput(name = "count", type = "java.lang.Integer", label = "Count", description = "Number of flashes (1-15)") int count) {
111 getHandler().sendPck(PckGenerator.flickerOutput(output - 1, depth, ramp, count));
112 } catch (LcnException e) {
113 logger.warn("Could not send output flicker command: {}", e.getMessage());
117 @RuleAction(label = "send a custom text", description = "Send custom text to an LCN-GTxD display.")
118 public void sendDynamicText(
119 @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,
120 @ActionInput(name = "text", type = "java.lang.String", label = "Text", description = "The text to display (max. 60 chars/bytes)") @Nullable String textInput) {
122 String text = textInput;
128 // some LCN-GTxD don't display the text if it fits exactly in one chunk. Observed with GT10D 8.0.
129 if (text.getBytes(LcnDefs.LCN_ENCODING).length % DYN_TEXT_CHUNK_LENGTH == 0) {
133 // convert String to bytes to split the data every 12 bytes, because a unicode character can take more than
135 ByteBuffer bb = ByteBuffer.wrap(text.getBytes(LcnDefs.LCN_ENCODING));
137 if (bb.capacity() > DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT) {
138 logger.warn("Dynamic text truncated. Has {} bytes: '{}'", bb.capacity(), text);
141 bb.limit(Math.min(DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT, bb.capacity()));
144 while (bb.hasRemaining()) {
145 byte[] chunk = new byte[DYN_TEXT_CHUNK_LENGTH];
146 bb.get(chunk, 0, Math.min(bb.remaining(), DYN_TEXT_CHUNK_LENGTH));
148 ByteBuffer command = ByteBuffer.allocate(DYN_TEXT_HEADER_LENGTH + DYN_TEXT_CHUNK_LENGTH);
149 command.put(PckGenerator.dynTextHeader(row - 1, part++).getBytes(LcnDefs.LCN_ENCODING));
152 getHandler().sendPck(command.array());
154 } catch (IllegalArgumentException | LcnException e) {
155 logger.warn("Could not send dynamic text: {}", e.getMessage());
160 * Start an lcn relay timer with the given duration [ms]
162 * @param relaynumber 1-based number of the relay to use
163 * @param duration duration of the relay timer in milliseconds
165 @RuleAction(label = "start a relay timer", description = "Start an LCN relay timer.")
166 public void startRelayTimer(
167 @ActionInput(name = "relaynumber", required = true, type = "java.lang.Integer", label = "Relay Number", description = "The relay number (1-8)") int relayNumber,
168 @ActionInput(name = "duration", required = true, type = "java.lang.Double", label = "Duration [ms]", description = "The timer duration in milliseconds") double duration) {
170 getHandler().sendPck(PckGenerator.startRelayTimer(relayNumber, duration));
171 } catch (LcnException e) {
172 logger.warn("Could not send start relay timer command: {}", e.getMessage());
176 /** Static alias to support the old DSL rules engine and make the action available there. */
177 public static void hitKey(ThingActions actions, @Nullable String table, int key, @Nullable String action) {
178 ((LcnModuleActions) actions).hitKey(table, key, action);
181 /** Static alias to support the old DSL rules engine and make the action available there. */
182 public static void flickerOutput(ThingActions actions, int output, int depth, int ramp, int count) {
183 ((LcnModuleActions) actions).flickerOutput(output, depth, ramp, count);
186 /** Static alias to support the old DSL rules engine and make the action available there. */
187 public static void sendDynamicText(ThingActions actions, int row, @Nullable String text) {
188 ((LcnModuleActions) actions).sendDynamicText(row, text);
191 /** Static alias to support the old DSL rules engine and make the action available there. */
192 public static void startRelayTimer(ThingActions actions, int relaynumber, double duration) {
193 ((LcnModuleActions) actions).startRelayTimer(relaynumber, duration);
196 private LcnModuleHandler getHandler() throws LcnException {
197 LcnModuleHandler localModuleHandler = moduleHandler;
198 if (localModuleHandler != null) {
199 return localModuleHandler;
201 throw new LcnException("Handler not set");