]> git.basschouten.com Git - openhab-addons.git/blob
d023fcc48cfd4b3c41a2f234b6bbff7fd0ea6c8a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.lang.reflect.Method;
16 import java.lang.reflect.Proxy;
17 import java.nio.ByteBuffer;
18 import java.util.Arrays;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.lcn.internal.common.LcnChannelGroup;
23 import org.openhab.binding.lcn.internal.common.LcnDefs;
24 import org.openhab.binding.lcn.internal.common.LcnDefs.KeyTable;
25 import org.openhab.binding.lcn.internal.common.LcnDefs.SendKeyCommand;
26 import org.openhab.binding.lcn.internal.common.LcnException;
27 import org.openhab.binding.lcn.internal.common.PckGenerator;
28 import org.openhab.core.automation.annotation.ActionInput;
29 import org.openhab.core.automation.annotation.RuleAction;
30 import org.openhab.core.thing.binding.ThingActions;
31 import org.openhab.core.thing.binding.ThingActionsScope;
32 import org.openhab.core.thing.binding.ThingHandler;
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 @ThingActionsScope(name = "lcn")
42 @NonNullByDefault
43 public class LcnModuleActions implements ThingActions, ILcnModuleActions {
44     private final Logger logger = LoggerFactory.getLogger(LcnModuleActions.class);
45     private static final int DYN_TEXT_CHUNK_COUNT = 5;
46     private static final int DYN_TEXT_HEADER_LENGTH = 6;
47     private static final int DYN_TEXT_CHUNK_LENGTH = 12;
48     private @Nullable LcnModuleHandler moduleHandler;
49
50     @Override
51     public void setThingHandler(@Nullable ThingHandler handler) {
52         this.moduleHandler = (LcnModuleHandler) handler;
53     }
54
55     @Override
56     public @Nullable ThingHandler getThingHandler() {
57         return moduleHandler;
58     }
59
60     @Override
61     @RuleAction(label = "LCN Hit Key", description = "Sends a \"hit key\" command to an LCN module")
62     public void hitKey(
63             @ActionInput(name = "table", required = true, type = "java.lang.String", label = "Table", description = "The key table (A-D)") @Nullable String table,
64             @ActionInput(name = "key", required = true, type = "java.lang.Integer", label = "Key", description = "The key number (1-8)") int key,
65             @ActionInput(name = "action", required = true, type = "java.lang.String", label = "Action", description = "The action (HIT, MAKE, BREAK)") @Nullable String action) {
66         try {
67             if (table == null) {
68                 throw new LcnException("Table is not set");
69             }
70
71             if (action == null) {
72                 throw new LcnException("Action is not set");
73             }
74
75             KeyTable keyTable;
76             try {
77                 keyTable = LcnDefs.KeyTable.valueOf(table.toUpperCase());
78             } catch (IllegalArgumentException e) {
79                 throw new LcnException("Unknown key table: " + table);
80             }
81
82             SendKeyCommand sendKeyCommand;
83             try {
84                 sendKeyCommand = SendKeyCommand.valueOf(action.toUpperCase());
85             } catch (IllegalArgumentException e) {
86                 throw new LcnException("Unknown action: " + action);
87             }
88
89             if (!LcnChannelGroup.KEYLOCKTABLEA.isValidId(key - 1)) {
90                 throw new LcnException("Key number is out of range: " + key);
91             }
92
93             SendKeyCommand[] cmds = new SendKeyCommand[LcnDefs.KEY_TABLE_COUNT];
94             Arrays.fill(cmds, SendKeyCommand.DONTSEND);
95             boolean[] keys = new boolean[LcnChannelGroup.KEYLOCKTABLEA.getCount()];
96
97             int keyTableNumber = keyTable.name().charAt(0) - LcnDefs.KeyTable.A.name().charAt(0);
98             cmds[keyTableNumber] = sendKeyCommand;
99             keys[key - 1] = true;
100
101             getHandler().sendPck(PckGenerator.sendKeys(cmds, keys));
102         } catch (LcnException e) {
103             logger.warn("Could not execute hit key command: {}", e.getMessage());
104         }
105     }
106
107     @Override
108     @RuleAction(label = "LCN Flicker Output", description = "Let a dimmer output flicker for a given count of flashes")
109     public void flickerOutput(
110             @ActionInput(name = "output", type = "java.lang.Integer", required = true, label = "Output", description = "The output number (1-4)") int output,
111             @ActionInput(name = "depth", type = "java.lang.Integer", label = "Depth", description = "0=25% 1=50% 2=100%") int depth,
112             @ActionInput(name = "ramp", type = "java.lang.Integer", label = "Ramp", description = "0=2sec 1=1sec 2=0.5sec") int ramp,
113             @ActionInput(name = "count", type = "java.lang.Integer", label = "Count", description = "Number of flashes (1-15)") int count) {
114         try {
115             getHandler().sendPck(PckGenerator.flickerOutput(output - 1, depth, ramp, count));
116         } catch (LcnException e) {
117             logger.warn("Could not send output flicker command: {}", e.getMessage());
118         }
119     }
120
121     @Override
122     @RuleAction(label = "LCN Dynamic 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             // convert String to bytes to split the data every 12 bytes, because a unicode character can take more than
134             // one byte
135             ByteBuffer bb = ByteBuffer.wrap(text.getBytes(LcnDefs.LCN_ENCODING));
136
137             if (bb.capacity() > DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT) {
138                 logger.warn("Dynamic text truncated. Has {} bytes: '{}'", bb.capacity(), text);
139             }
140
141             bb.limit(Math.min(DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT, bb.capacity()));
142
143             int part = 0;
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));
147
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));
150                 command.put(chunk);
151
152                 getHandler().sendPck(command.array());
153             }
154         } catch (IllegalArgumentException | LcnException e) {
155             logger.warn("Could not send dynamic text: {}", e.getMessage());
156         }
157     }
158
159     /**
160      * Start an lcn relay timer with the given duration [ms]
161      *
162      * @param relaynumber 1-based number of the relay to use
163      * @param duration duration of the relay timer in milliseconds
164      */
165     @Override
166     @RuleAction(label = "LCN Relay Timer", description = "Start an LCN relay timer")
167     public void startRelayTimer(
168             @ActionInput(name = "relaynumber", required = true, type = "java.lang.Integer", label = "Relay Number", description = "The relay number (1-8)") int relayNumber,
169             @ActionInput(name = "duration", required = true, type = "java.lang.Double", label = "Duration [ms]", description = "The timer duration in milliseconds") double duration) {
170         try {
171             getHandler().sendPck(PckGenerator.startRelayTimer(relayNumber, duration));
172         } catch (LcnException e) {
173             logger.warn("Could not send start relay timer command: {}", e.getMessage());
174         }
175     }
176
177     private static ILcnModuleActions invokeMethodOf(@Nullable ThingActions actions) {
178         if (actions == null) {
179             throw new IllegalArgumentException("actions cannot be null");
180         }
181         if (actions.getClass().getName().equals(LcnModuleActions.class.getName())) {
182             if (actions instanceof LcnModuleActions) {
183                 return (ILcnModuleActions) actions;
184             } else {
185                 return (ILcnModuleActions) Proxy.newProxyInstance(ILcnModuleActions.class.getClassLoader(),
186                         new Class[] { ILcnModuleActions.class }, (Object proxy, Method method, Object[] args) -> {
187                             Method m = actions.getClass().getDeclaredMethod(method.getName(),
188                                     method.getParameterTypes());
189                             return m.invoke(actions, args);
190                         });
191             }
192         }
193         throw new IllegalArgumentException("Actions is not an instance of LcnModuleActions");
194     }
195
196     /** Static alias to support the old DSL rules engine and make the action available there. */
197     public static void hitKey(@Nullable ThingActions actions, @Nullable String table, int key,
198             @Nullable String action) {
199         invokeMethodOf(actions).hitKey(table, key, action);
200     }
201
202     /** Static alias to support the old DSL rules engine and make the action available there. */
203     public static void flickerOutput(@Nullable ThingActions actions, int output, int depth, int ramp, int count) {
204         invokeMethodOf(actions).flickerOutput(output, depth, ramp, count);
205     }
206
207     /** Static alias to support the old DSL rules engine and make the action available there. */
208     public static void sendDynamicText(@Nullable ThingActions actions, int row, @Nullable String text) {
209         invokeMethodOf(actions).sendDynamicText(row, text);
210     }
211
212     /** Static alias to support the old DSL rules engine and make the action available there. */
213     public static void startRelayTimer(@Nullable ThingActions actions, int relaynumber, double duration) {
214         invokeMethodOf(actions).startRelayTimer(relaynumber, duration);
215     }
216
217     private LcnModuleHandler getHandler() throws LcnException {
218         LcnModuleHandler localModuleHandler = moduleHandler;
219         if (localModuleHandler != null) {
220             return localModuleHandler;
221         } else {
222             throw new LcnException("Handler not set");
223         }
224     }
225 }