2 * Copyright (c) 2010-2023 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.irtrans.internal;
15 import java.io.UnsupportedEncodingException;
16 import java.nio.ByteBuffer;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayList;
19 import java.util.List;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
25 * The {@link IrCommand} is a structure to store and manipulate infrared command
28 * @author Karel Goderis - Initial contribution
31 public class IrCommand {
33 private Logger logger = LoggerFactory.getLogger(IrCommand.class);
37 * Each infrared command is in essence a sequence of characters/pointers
38 * that refer to pulse/pause timing pairs. So, in order to build an infrared
39 * command one has to collate the pulse/pause timings as defined by the
42 * PulsePair is a small datastructure to capture each pulse/pair timing pair
45 private class PulsePair {
50 private String remote;
51 private String command;
52 private String sequence;
53 private List<PulsePair> pulsePairs;
54 private int numberOfRepeats;
55 private int frequency;
56 private int frameLength;
58 private boolean startBit;
59 private boolean repeatStartBit;
60 private boolean noTog;
64 public String getRemote() {
68 public void setRemote(String remote) {
72 public String getCommand() {
76 public void setCommand(String command) {
77 this.command = command;
80 public String getSequence() {
84 public void setSequence(String sequence) {
85 this.sequence = sequence;
88 public List<PulsePair> getPulsePairs() {
92 public void setPulsePairs(ArrayList<PulsePair> pulsePairs) {
93 this.pulsePairs = pulsePairs;
96 public int getNumberOfRepeats() {
97 return numberOfRepeats;
100 public void setNumberOfRepeats(int numberOfRepeats) {
101 this.numberOfRepeats = numberOfRepeats;
104 public int getFrequency() {
108 public void setFrequency(int frequency) {
109 this.frequency = frequency;
112 public int getFrameLength() {
116 public void setFrameLength(int frameLength) {
117 this.frameLength = frameLength;
120 public int getPause() {
124 public void setPause(int pause) {
128 public boolean isStartBit() {
132 public void setStartBit(boolean startBit) {
133 this.startBit = startBit;
136 public boolean isRepeatStartBit() {
137 return repeatStartBit;
140 public void setRepeatStartBit(boolean repeatStartBit) {
141 this.repeatStartBit = repeatStartBit;
144 public boolean isNoTog() {
148 public void setNoTog(boolean noTog) {
152 public boolean isRc5() {
156 public void setRc5(boolean rc5) {
160 public boolean isRc6() {
164 public void setRc6(boolean rc6) {
169 * Matches two IrCommands Commands match if they have the same remote and
172 * @param anotherCommand the another command
173 * @return true, if successful
175 public boolean matches(IrCommand anotherCommand) {
176 return (matchRemote(anotherCommand) && matchCommand(anotherCommand));
180 * Match remote fields of two IrCommands In everything we do in the IRtrans
181 * binding, the "*" stands for a wilcard character and will match anything
183 * @param S the infrared command
184 * @return true, if successful
186 private boolean matchRemote(IrCommand S) {
187 if ("*".equals(getRemote()) || "*".equals(S.getRemote())) {
190 return S.getRemote().equals(getRemote());
195 * Match command fields of two IrCommands
197 * @param S the infrared command
198 * @return true, if successful
200 private boolean matchCommand(IrCommand S) {
201 if ("*".equals(command) || "*".equals(S.command)) {
204 return S.command.equals(command);
209 * Convert/Parse the IRCommand into a ByteBuffer that is compatible with the
212 * @return the byte buffer
214 public ByteBuffer toByteBuffer() {
215 ByteBuffer byteBuffer = ByteBuffer.allocate(44 + 210 + 1);
217 // skip first byte for length - we will fill it in at the end
218 byteBuffer.position(1);
220 // Checksum - 1 byte - not used in the ethernet version of the device
221 byteBuffer.put((byte) 0);
223 // Command - 1 byte - not used
224 byteBuffer.put((byte) 0);
226 // Address - 1 byte - not used
227 byteBuffer.put((byte) 0);
229 // Mask - 2 bytes - not used
230 byteBuffer.putShort((short) 0);
232 // Number of pulse pairs - 1 byte
233 byte[] byteSequence = sequence.getBytes(StandardCharsets.US_ASCII);
234 byteBuffer.put((byte) (byteSequence.length));
236 // Frequency - 1 byte
237 byteBuffer.put((byte) frequency);
239 // Mode / Flags - 1 byte
242 modeFlags = (byte) (modeFlags | 1);
244 if (repeatStartBit) {
245 modeFlags = (byte) (modeFlags | 2);
248 modeFlags = (byte) (modeFlags | 4);
251 modeFlags = (byte) (modeFlags | 8);
253 byteBuffer.put(modeFlags);
255 // Pause timings - 8 Shorts = 16 bytes
256 for (int i = 0; i < pulsePairs.size(); i++) {
257 byteBuffer.putShort((short) Math.round(pulsePairs.get(i).pause / 8));
259 for (int i = pulsePairs.size(); i <= 7; i++) {
260 byteBuffer.putShort((short) 0);
263 // Pulse timings - 8 Shorts = 16 bytes
264 for (int i = 0; i < pulsePairs.size(); i++) {
265 byteBuffer.putShort((short) Math.round(pulsePairs.get(i).pulse / 8));
267 for (int i = pulsePairs.size(); i <= 7; i++) {
268 byteBuffer.putShort((short) 0);
271 // Time Counts - 1 Byte
272 byteBuffer.put((byte) pulsePairs.size());
275 byte repeat = (byte) 0;
276 repeat = (byte) numberOfRepeats;
277 if (frameLength > 0) {
278 repeat = (byte) (repeat | 128);
280 byteBuffer.put(repeat);
282 // Repeat Pause or Frame Length - 1 byte
283 if ((repeat & 128) == 128) {
284 byteBuffer.put((byte) frameLength);
286 byteBuffer.put((byte) pause);
291 byteBuffer.put(sequence.getBytes("ASCII"));
292 } catch (UnsupportedEncodingException e) {
293 logger.warn("An exception occurred while encoding the sequence : '{}'", e.getMessage(), e);
296 // Add <CR> (ASCII 13) at the end of the sequence
297 byteBuffer.put((byte) ((char) 13));
299 // set the length of the byte sequence
301 byteBuffer.position(0);
302 byteBuffer.put((byte) (byteBuffer.limit() - 1));
303 byteBuffer.position(0);
309 * Convert the the infrared command to a Hexadecimal notation/string that
310 * can be interpreted by the IRTrans device
312 * Convert the first 44 bytes to hex notation, then copy the remainder (= IR
313 * command piece) as ASCII string
315 * @return the byte buffer in Hex format
317 public ByteBuffer toHEXByteBuffer() {
318 byte[] hexDigit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
320 ByteBuffer byteBuffer = toByteBuffer();
321 byte[] toConvert = new byte[byteBuffer.limit()];
322 byteBuffer.get(toConvert, 0, byteBuffer.limit());
324 byte[] converted = new byte[toConvert.length * 2];
326 for (int k = 0; k < toConvert.length - 1; k++) {
327 converted[2 * k] = hexDigit[(toConvert[k] >> 4) & 0x0f];
328 converted[2 * k + 1] = hexDigit[toConvert[k] & 0x0f];
332 ByteBuffer convertedBuffer = ByteBuffer.allocate(converted.length);
333 convertedBuffer.put(converted);
334 convertedBuffer.flip();
336 return convertedBuffer;
340 * Convert 'sequence' bit of the IRTrans compatible byte buffer to a
345 public String sequenceToHEXString() {
346 byte[] byteArray = toHEXByteArray();
347 return new String(byteArray, 88, byteArray.length - 88 - 2);
351 * Convert the IRTrans compatible byte buffer to a string
355 public String toHEXString() {
356 return new String(toHEXByteArray());
360 * Convert the IRTrans compatible byte buffer to a byte array.
364 public byte[] toHEXByteArray() {
365 return toHEXByteBuffer().array();