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.openthermgateway.internal;
15 import java.util.regex.Pattern;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
21 * The {@link Message} represent a single message received from the OpenTherm Gateway.
23 * @author Arjen Korevaar - Initial contribution
26 public class Message {
28 private static final Pattern MESSAGEPATTERN = Pattern.compile("[TBRA]{1}[A-F0-9]{8}");
30 private CodeType codeType;
31 private MessageType messageType;
35 public CodeType getCodeType() {
39 public MessageType getMessageType() {
47 public @Nullable String getData(ByteType byteType) {
48 if (this.data.length() == 4) {
51 return this.data.substring(0, 2);
53 return this.data.substring(2, 4);
62 public boolean getBit(ByteType byteType, int pos) {
64 String data = getData(byteType);
67 // First parse the hex value to an integer
68 int parsed = Integer.parseInt(data, 16);
70 // Then right shift it pos positions so that the required bit is at the front
71 // and then apply a bitmask of 00000001 (1)
72 return ((parsed >> pos) & 1) == 1;
78 public int getUInt(ByteType byteType) {
80 String data = getData(byteType);
83 return Integer.parseInt(data, 16);
89 public int getInt(ByteType byteType) {
91 String data = getData(byteType);
94 return parseSignedInteger(data);
100 public float getFloat() {
101 // f8.8, two's complement
103 String data = getData(ByteType.BOTH);
106 long value = Long.parseLong(data, 16);
108 // left padded with zeros
109 String binary = String.format("%16s", Long.toBinaryString(value)).replace(' ', '0');
111 if (binary.charAt(0) == '1') {
114 String inverted = invertBinary(binary);
116 value = Long.parseLong(inverted, 2);
121 // divide by 2^8 = 256
122 return (float) value / 256;
128 public boolean overrides(@Nullable Message other) {
129 // If the message is a Request sent to the boiler or an Answer returned to the
130 // thermostat, and it's ID is equal to the previous message, then this is an
131 // override sent by the OpenTherm Gateway
132 return other != null && this.getID() == other.getID() && (codeType == CodeType.R || codeType == CodeType.A);
136 public String toString() {
137 return String.format("%s - %s - %s", this.codeType, this.id, this.data);
140 public Message(CodeType codeType, MessageType messageType, int id, String data) {
141 this.codeType = codeType;
142 this.messageType = messageType;
147 public static @Nullable Message parse(String message) {
148 if (MESSAGEPATTERN.matcher(message).matches()) {
149 // For now, only parse TBRA codes
150 CodeType codeType = CodeType.valueOf(message.substring(0, 1));
151 MessageType messageType = getMessageType(message.substring(1, 3));
152 int id = Integer.valueOf(message.substring(3, 5), 16);
153 String data = message.substring(5);
155 return new Message(codeType, messageType, id, data);
161 private static MessageType getMessageType(String value) {
162 // First parse the hex value to an integer
163 int integer = Integer.parseInt(value, 16);
165 // Then right shift it 4 bits so that the message type bits are at the front
166 int shifted = integer >> 4;
168 // Then mask it with 00000111 (7), so that we only get the first 3 bits,
169 // effectively cutting off the parity bit.
170 int cutoff = shifted & 7;
174 return MessageType.READDATA;
176 return MessageType.WRITEDATA;
178 return MessageType.INVALIDDATA;
180 return MessageType.RESERVED;
182 return MessageType.READACK;
184 return MessageType.WRITEACK;
186 return MessageType.DATAINVALID;
189 return MessageType.UNKNOWNDATAID;
193 private int parseSignedInteger(String data) {
194 // First parse the hex value to an unsigned integer value
195 int result = Integer.parseInt(data, 16);
197 if (data.length() == 4) {
198 // This is a two byte value, apply a bitmask of 01111111 11111111 (32767) to cut
200 result = result & 32767;
202 // Then apply a bitmask of 10000000 00000000 (32768) to check the sign bit
203 if ((result & 32768) == 32768) {
204 // If the sign is 1000000 00000000 (32768) then it's a negative
205 result = -32768 + result;
208 // This is a one byte value, apply a bitmask of 01111111 (127), to cut off the
210 result = result & 127;
212 // Then apply a bitmask of 10000000 (128) to check the sign bit
213 if ((result & 128) == 128) {
214 // If the sign is 1000000 (128) then it's a negative
215 result = -128 + result;
222 private String invertBinary(String value) {
223 // There is probably a better solution, but for now this works
224 String result = value;
226 result = result.replace('1', 'X');
227 result = result.replace('0', '1');
228 result = result.replace('X', '0');