]> git.basschouten.com Git - openhab-addons.git/blob
ff37573871d32590ee1131831a812ca1435ad7b5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.satel.internal.protocol;
14
15 import java.util.Arrays;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * Represents a message sent to (a command) or received from (a response)
24  * {@link SatelModule}. It consists of one byte that specifies command type or
25  * response status and certain number of payload bytes. Number of payload byte
26  * depends on command type. The class allows to serialize a command to bytes and
27  * deserialize a response from given bytes. It also computes and validates
28  * message checksum.
29  *
30  * @author Krzysztof Goworek - Initial contribution
31  */
32 @NonNullByDefault
33 public class SatelMessage {
34     private static final Logger LOGGER = LoggerFactory.getLogger(SatelMessage.class);
35
36     private byte command;
37     private byte[] payload;
38
39     protected static final byte[] EMPTY_PAYLOAD = new byte[0];
40
41     /**
42      * Creates new instance with specified command code and payload.
43      *
44      * @param command
45      *            command code
46      * @param payload
47      *            command payload
48      */
49     public SatelMessage(byte command, byte[] payload) {
50         this.command = command;
51         this.payload = payload;
52     }
53
54     /**
55      * Creates new instance with specified command code and empty payload.
56      *
57      * @param command command code
58      */
59     public SatelMessage(byte command) {
60         this(command, EMPTY_PAYLOAD);
61     }
62
63     /**
64      * Deserializes new message instance from specified byte buffer.
65      *
66      * @param buffer bytes to deserialize a message from
67      * @return deserialized message instance
68      */
69     public static @Nullable SatelMessage fromBytes(byte[] buffer) {
70         // we need at least command and checksum
71         if (buffer.length < 3) {
72             LOGGER.error("Invalid message length: {}", buffer.length);
73             return null;
74         }
75
76         // check crc
77         int receivedCrc = 0xffff & ((buffer[buffer.length - 2] << 8) | (buffer[buffer.length - 1] & 0xff));
78         int expectedCrc = calculateChecksum(buffer, buffer.length - 2);
79         if (receivedCrc != expectedCrc) {
80             LOGGER.error("Invalid message checksum: received = {}, expected = {}", receivedCrc, expectedCrc);
81             return null;
82         }
83
84         SatelMessage message = new SatelMessage(buffer[0], new byte[buffer.length - 3]);
85         if (message.payload.length > 0) {
86             System.arraycopy(buffer, 1, message.payload, 0, buffer.length - 3);
87         }
88         return message;
89     }
90
91     /**
92      * Returns command byte.
93      *
94      * @return the command
95      */
96     public byte getCommand() {
97         return this.command;
98     }
99
100     /**
101      * Returns the payload bytes.
102      *
103      * @return payload as byte array
104      */
105     public byte[] getPayload() {
106         return this.payload;
107     }
108
109     /**
110      * Returns the message serialized as array of bytes with checksum calculated
111      * at last two bytes.
112      *
113      * @return the message as array of bytes
114      */
115     public byte[] getBytes() {
116         byte buffer[] = new byte[this.payload.length + 3];
117         buffer[0] = this.command;
118         if (this.payload.length > 0) {
119             System.arraycopy(this.payload, 0, buffer, 1, this.payload.length);
120         }
121         int checksum = calculateChecksum(buffer, buffer.length - 2);
122         buffer[buffer.length - 2] = (byte) ((checksum >> 8) & 0xff);
123         buffer[buffer.length - 1] = (byte) (checksum & 0xff);
124         return buffer;
125     }
126
127     private String getPayloadAsHex() {
128         StringBuilder result = new StringBuilder();
129         for (int i = 0; i < this.payload.length; ++i) {
130             if (i > 0) {
131                 result.append(" ");
132             }
133             result.append(String.format("%02X", this.payload[i]));
134         }
135         return result.toString();
136     }
137
138     /**
139      * Calculates a checksum for the specified buffer.
140      *
141      * @param buffer the buffer to calculate.
142      * @return the checksum value.
143      */
144     private static int calculateChecksum(byte[] buffer, int length) {
145         int checkSum = 0x147a;
146         for (int i = 0; i < length; i++) {
147             checkSum = ((checkSum << 1) | ((checkSum >> 15) & 1));
148             checkSum ^= 0xffff;
149             checkSum += ((checkSum >> 8) & 0xff) + (buffer[i] & 0xff);
150         }
151         checkSum &= 0xffff;
152         LOGGER.trace("Calculated checksum = {}", String.format("%04X", checkSum));
153         return checkSum;
154     }
155
156     @Override
157     public String toString() {
158         return String.format("Message: command = %02X, payload = %s", this.command, getPayloadAsHex());
159     }
160
161     @SuppressWarnings("PMD.SimplifyBooleanReturns")
162     @Override
163     public boolean equals(@Nullable Object obj) {
164         if (this == obj) {
165             return true;
166         }
167
168         if (obj == null) {
169             return false;
170         }
171
172         if (!obj.getClass().equals(this.getClass())) {
173             return false;
174         }
175
176         SatelMessage other = (SatelMessage) obj;
177
178         if (other.command != this.command) {
179             return false;
180         }
181
182         if (!Arrays.equals(other.payload, this.payload)) {
183             return false;
184         }
185
186         return true;
187     }
188
189     @Override
190     public int hashCode() {
191         final int prime = 31;
192         int result = 1;
193         result = prime * result + command;
194         result = prime * result + Arrays.hashCode(payload);
195         return result;
196     }
197 }