]> git.basschouten.com Git - openhab-addons.git/blob
649ce2e0f42ab55b89ecf51abaa1decc9b8811cb
[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.insteon.internal.message;
14
15 import java.io.IOException;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.insteon.internal.utils.Utils;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 /**
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).
31  *
32  * @author Bernd Pfrommer - Initial contribution
33  * @author Rob Nielsen - Port to openHAB 2 insteon binding
34  */
35 @NonNullByDefault
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
44
45     /**
46      * Constructor
47      */
48     public MsgFactory() {
49     }
50
51     /**
52      * Indicates if no more complete message available in the buffer to be processed
53      *
54      * @return buffer data fully processed flag
55      */
56     public boolean isDone() {
57         return done;
58     }
59
60     /**
61      * Adds incoming data to the data buffer. First call addData(), then call processData()
62      *
63      * @param data data to be added
64      * @param len length of data to be added
65      */
66     public void addData(byte[] data, int len) {
67         int l = len;
68         if (l + end > MAX_MSG_LEN) {
69             logger.warn("truncating excessively long message!");
70             l = MAX_MSG_LEN - end;
71         }
72         // indicate new data can be processed if length > 0
73         if (l > 0) {
74             done = false;
75         }
76         // append the new data to the one we already have
77         System.arraycopy(data, 0, buf, end, l);
78         end += l;
79         // copy the incoming data to the end of the buffer
80         logger.trace("read buffer: len {} data: {}", end, Utils.getHexString(buf, end));
81     }
82
83     /**
84      * After data has been added, this method processes it.
85      * processData() needs to be called until it returns null, indicating that no
86      * more messages can be formed from the data buffer.
87      *
88      * @return a valid message, or null if the message is not complete
89      * @throws IOException if data was received with unknown command codes
90      */
91     public @Nullable Msg processData() throws IOException {
92         Msg msg = null;
93         // handle the case where we get a pure nack
94         if (end > 0 && buf[0] == 0x15) {
95             logger.trace("got pure nack!");
96             removeFromBuffer(1);
97             try {
98                 msg = Msg.makeMessage("PureNACK");
99                 return msg;
100             } catch (InvalidMessageTypeException e) {
101                 return null;
102             }
103         }
104         // drain the buffer until the first byte is 0x02
105         if (end > 0 && buf[0] != 0x02) {
106             bail("incoming message does not start with 0x02");
107         }
108         // Now see if we have enough data for a complete message.
109         // If not, we return null, and expect this method to be called again
110         // when more data has come in.
111         if (end > 1) {
112             // we have some data, but do we have enough to read the entire header?
113             int headerLength = Msg.getHeaderLength(buf[1]);
114             boolean isExtended = Msg.isExtended(buf, end, headerLength);
115             logger.trace("header length expected: {} extended: {}", headerLength, isExtended);
116             if (headerLength < 0) {
117                 removeFromBuffer(1); // get rid of the leading 0x02 so draining works
118                 bail("got unknown command code " + Utils.getHexByte(buf[0]));
119             } else if (headerLength >= 2) {
120                 if (end >= headerLength) {
121                     // only when the header is complete do we know that isExtended is correct!
122                     int msgLen = Msg.getMessageLength(buf[1], isExtended);
123                     logger.trace("msgLen expected: {}", msgLen);
124                     if (msgLen < 0) {
125                         // Cannot make sense out of the combined command code & isExtended flag.
126                         removeFromBuffer(1);
127                         bail("got unknown command code/ext flag " + Utils.getHexByte(buf[0]));
128                     } else if (msgLen > 0) {
129                         if (end >= msgLen) {
130                             msg = Msg.createMessage(buf, msgLen, isExtended);
131                             removeFromBuffer(msgLen);
132                         }
133                     } else { // should never happen
134                         logger.warn("invalid message length, internal error!");
135                     }
136                 }
137             } else { // should never happen
138                 logger.warn("invalid header length, internal error!");
139             }
140         }
141         // indicate no more messages available in buffer if empty or undefined message
142         if (end == 0 || msg == null) {
143             logger.trace("done processing current buffer data");
144             done = true;
145         }
146         logger.trace("keeping buffer len {} data: {}", end, Utils.getHexString(buf, end));
147         return msg;
148     }
149
150     private void bail(String txt) throws IOException {
151         drainBuffer(); // this will drain until end or it finds the next 0x02
152         logger.debug("bad data received: {}", txt);
153         throw new IOException(txt);
154     }
155
156     private void drainBuffer() {
157         while (end > 0 && buf[0] != 0x02) {
158             removeFromBuffer(1);
159         }
160     }
161
162     private void removeFromBuffer(int len) {
163         int l = len;
164         if (l > end) {
165             l = end;
166         }
167         System.arraycopy(buf, l, buf, 0, end + 1 - l);
168         end -= l;
169     }
170 }