]> git.basschouten.com Git - openhab-addons.git/blob
7be9efc9b99ec0bf207224aa8020078b4f9627d8
[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.openthermgateway.internal;
14
15 import java.util.regex.Pattern;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19
20 /**
21  * The {@link Message} represent a single message received from the OpenTherm Gateway.
22  *
23  * @author Arjen Korevaar - Initial contribution
24  */
25 @NonNullByDefault
26 public class Message {
27
28     private static final Pattern MESSAGEPATTERN = Pattern.compile("[TBRA]{1}[A-F0-9]{8}");
29
30     private CodeType codeType;
31     private MessageType messageType;
32     private int id;
33     private String data;
34
35     public CodeType getCodeType() {
36         return codeType;
37     }
38
39     public MessageType getMessageType() {
40         return messageType;
41     }
42
43     public int getID() {
44         return id;
45     }
46
47     public @Nullable String getData(ByteType byteType) {
48         if (this.data.length() == 4) {
49             switch (byteType) {
50                 case HIGHBYTE:
51                     return this.data.substring(0, 2);
52                 case LOWBYTE:
53                     return this.data.substring(2, 4);
54                 case BOTH:
55                     return this.data;
56             }
57         }
58
59         return null;
60     }
61
62     public boolean getBit(ByteType byteType, int pos) {
63         @Nullable
64         String data = getData(byteType);
65
66         if (data != null) {
67             // First parse the hex value to an integer
68             int parsed = Integer.parseInt(data, 16);
69
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;
73         }
74
75         return false;
76     }
77
78     public int getUInt(ByteType byteType) {
79         @Nullable
80         String data = getData(byteType);
81
82         if (data != null) {
83             return Integer.parseInt(data, 16);
84         }
85
86         return 0;
87     }
88
89     public int getInt(ByteType byteType) {
90         @Nullable
91         String data = getData(byteType);
92
93         if (data != null) {
94             return parseSignedInteger(data);
95         }
96
97         return 0;
98     }
99
100     public float getFloat() {
101         // f8.8, two's complement
102         @Nullable
103         String data = getData(ByteType.BOTH);
104
105         if (data != null) {
106             long value = Long.parseLong(data, 16);
107
108             // left padded with zeros
109             String binary = String.format("%16s", Long.toBinaryString(value)).replace(' ', '0');
110
111             if (binary.charAt(0) == '1') {
112                 // negative value
113
114                 String inverted = invertBinary(binary);
115
116                 value = Long.parseLong(inverted, 2);
117                 value = value + 1;
118                 value = value * -1;
119             }
120
121             // divide by 2^8 = 256
122             return (float) value / 256;
123         }
124
125         return 0;
126     }
127
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);
133     }
134
135     @Override
136     public String toString() {
137         return String.format("%s - %s - %s", this.codeType, this.id, this.data);
138     }
139
140     public Message(CodeType codeType, MessageType messageType, int id, String data) {
141         this.codeType = codeType;
142         this.messageType = messageType;
143         this.id = id;
144         this.data = data;
145     }
146
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);
154
155             return new Message(codeType, messageType, id, data);
156         }
157
158         return null;
159     }
160
161     private static MessageType getMessageType(String value) {
162         // First parse the hex value to an integer
163         int integer = Integer.parseInt(value, 16);
164
165         // Then right shift it 4 bits so that the message type bits are at the front
166         int shifted = integer >> 4;
167
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;
171
172         switch (cutoff) {
173             case 0: // 000
174                 return MessageType.READDATA;
175             case 1: // 001
176                 return MessageType.WRITEDATA;
177             case 2: // 010
178                 return MessageType.INVALIDDATA;
179             case 3: // 011
180                 return MessageType.RESERVED;
181             case 4: // 100
182                 return MessageType.READACK;
183             case 5: // 101
184                 return MessageType.WRITEACK;
185             case 6: // 110
186                 return MessageType.DATAINVALID;
187             case 7: // 111
188             default:
189                 return MessageType.UNKNOWNDATAID;
190         }
191     }
192
193     private int parseSignedInteger(String data) {
194         // First parse the hex value to an unsigned integer value
195         int result = Integer.parseInt(data, 16);
196
197         if (data.length() == 4) {
198             // This is a two byte value, apply a bitmask of 01111111 11111111 (32767) to cut
199             // off the sign bit
200             result = result & 32767;
201
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;
206             }
207         } else {
208             // This is a one byte value, apply a bitmask of 01111111 (127), to cut off the
209             // sign bit
210             result = result & 127;
211
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;
216             }
217         }
218
219         return result;
220     }
221
222     private String invertBinary(String value) {
223         // There is probably a better solution, but for now this works
224         String result = value;
225
226         result = result.replace('1', 'X');
227         result = result.replace('0', '1');
228         result = result.replace('X', '0');
229
230         return result;
231     }
232 }