2 * Copyright (c) 2010-2024 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.insteon.internal.transport.message;
15 import java.io.IOException;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.insteon.internal.utils.HexUtils;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
24 * This class takes data coming from the serial port and turns it
25 * into a message. For that, it has to figure out the length of the
26 * message from the header, and read enough bytes until it hits the
27 * message boundary. The code is tricky, partly because the Insteon protocol is.
28 * Most of the time the command code (second byte) is enough to determine the length
29 * of the incoming message, but sometimes one has to look deeper into the message
30 * to determine if it is a standard or extended message (their lengths differ).
32 * @author Bernd Pfrommer - Initial contribution
33 * @author Rob Nielsen - Port to openHAB 2 insteon binding
36 public class MsgFactory {
37 private final Logger logger = LoggerFactory.getLogger(MsgFactory.class);
38 // no idea what the max msg length could be, but
39 // I doubt it'll ever be larger than 4k
40 private static final int MAX_MSG_LEN = 4096;
41 private byte[] buf = new byte[MAX_MSG_LEN];
42 private int end = 0; // offset of end of buffer
43 private boolean done = true; // done fully processing buffer flag
52 * Indicates if no more complete message available in the buffer to be processed
54 * @return buffer data fully processed flag
56 public boolean isDone() {
61 * Adds incoming data to the data buffer. First call addData(), then call processData()
63 * @param data data to be added
64 * @param len length of data to be added
66 public void addData(byte[] data, int len) {
68 if (l + end > MAX_MSG_LEN) {
69 logger.warn("truncating excessively long message!");
70 l = MAX_MSG_LEN - end;
72 // indicate new data can be processed if length > 0
76 // append the new data to the one we already have
77 System.arraycopy(data, 0, buf, end, l);
79 // copy the incoming data to the end of the buffer
80 if (logger.isTraceEnabled()) {
81 logger.trace("read buffer: len {} data: {}", end, HexUtils.getHexString(buf, end, false));
86 * After data has been added, this method processes it.
87 * processData() needs to be called until it returns null, indicating that no
88 * more messages can be formed from the data buffer.
90 * @return a valid message, or null if the message is not complete
91 * @throws IOException if data was received with unknown command codes
93 public @Nullable Msg processData() throws IOException {
95 // handle the case where we get a pure nack
96 if (end > 0 && buf[0] == 0x15) {
97 logger.trace("got pure nack!");
100 msg = Msg.makeMessage("PureNACK");
102 } catch (InvalidMessageTypeException e) {
106 // drain the buffer until the first byte is 0x02
107 if (end > 0 && buf[0] != 0x02) {
108 logger.debug("incoming message does not start with 0x02");
111 // Now see if we have enough data for a complete message.
112 // If not, we return null, and expect this method to be called again
113 // when more data has come in.
115 // we have some data, but do we have enough to read the entire header?
116 int headerLength = Msg.getHeaderLength(buf[1]);
117 boolean isExtended = Msg.isExtended(buf, end, headerLength);
118 logger.trace("header length expected: {} extended: {}", headerLength, isExtended);
119 if (headerLength < 0) {
120 if (logger.isDebugEnabled()) {
121 logger.debug("got unknown command code: {}", HexUtils.getHexString(buf[1]));
123 removeFromBuffer(1); // get rid of the leading 0x02 so draining works
125 } else if (headerLength >= 2) {
126 if (end >= headerLength) {
127 // only when the header is complete do we know that isExtended is correct!
128 int msgLen = Msg.getMessageLength(buf[1], isExtended);
129 logger.trace("msgLen expected: {}", msgLen);
131 // Cannot make sense out of the combined command code & isExtended flag.
132 if (logger.isDebugEnabled()) {
133 logger.debug("got unknown command code/ext flag: {}", HexUtils.getHexString(buf[1]));
137 } else if (msgLen > 0) {
139 msg = Msg.createMessage(buf, msgLen, isExtended);
140 removeFromBuffer(msgLen);
142 } else { // should never happen
143 logger.warn("invalid message length, internal error!");
146 } else { // should never happen
147 logger.warn("invalid header length, internal error!");
150 // indicate no more messages available in buffer if empty or undefined message
151 if (end == 0 || msg == null) {
152 logger.trace("done processing current buffer data");
155 if (logger.isTraceEnabled()) {
156 logger.trace("keeping buffer len {} data: {}", end, HexUtils.getHexString(buf, end, false));
161 private void bail() throws IOException {
162 drainBuffer(); // this will drain until end or it finds the next message start
163 throw new IOException("bad data received");
166 private void drainBuffer() {
167 while (end > 0 && buf[0] != 0x02) {
172 private void removeFromBuffer(int len) {
177 System.arraycopy(buf, l, buf, 0, end + 1 - l);