]> git.basschouten.com Git - openhab-addons.git/blob
fc9ce0c0a4fae9e720d6a3ccbeea9725db640ed4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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     /*
31      * The code field is not part of OpenTherm specification, but added by OpenTherm Gateway.
32      * It can be any of the following:
33      *
34      * T: Message received from the thermostat
35      * B: Message received from the boiler
36      * R: Request sent to the boiler
37      * A: Response returned to the thermostat
38      * E: Parity or stop bit error
39      */
40     private String code;
41     private MessageType messageType;
42     private int id;
43     private String data;
44
45     public String getCode() {
46         return this.code;
47     }
48
49     public MessageType getMessageType() {
50         return messageType;
51     }
52
53     public int getID() {
54         return id;
55     }
56
57     public @Nullable String getData(ByteType byteType) {
58         if (this.data.length() == 4) {
59             switch (byteType) {
60                 case HIGHBYTE:
61                     return this.data.substring(0, 2);
62                 case LOWBYTE:
63                     return this.data.substring(2, 4);
64                 case BOTH:
65                     return this.data;
66             }
67         }
68
69         return null;
70     }
71
72     public boolean getBit(ByteType byteType, int pos) {
73         @Nullable
74         String data = getData(byteType);
75
76         if (data != null) {
77             // First parse the hex value to an integer
78             int parsed = Integer.parseInt(data, 16);
79
80             // Then right shift it pos positions so that the required bit is at the front
81             // and then apply a bitmask of 00000001 (1)
82             return ((parsed >> pos) & 1) == 1;
83         }
84
85         return false;
86     }
87
88     public int getUInt(ByteType byteType) {
89         @Nullable
90         String data = getData(byteType);
91
92         if (data != null) {
93             return Integer.parseInt(data, 16);
94         }
95
96         return 0;
97     }
98
99     public int getInt(ByteType byteType) {
100         @Nullable
101         String data = getData(byteType);
102
103         if (data != null) {
104             return parseSignedInteger(data);
105         }
106
107         return 0;
108     }
109
110     public float getFloat() {
111         // f8.8, two's complement
112         @Nullable
113         String data = getData(ByteType.BOTH);
114
115         if (data != null) {
116             long value = Long.parseLong(data, 16);
117
118             // left padded with zeros
119             String binary = String.format("%16s", Long.toBinaryString(value)).replace(' ', '0');
120
121             if (binary.charAt(0) == '1') {
122                 // negative value
123
124                 String inverted = invertBinary(binary);
125
126                 value = Long.parseLong(inverted, 2);
127                 value = value + 1;
128                 value = value * -1;
129             }
130
131             // divide by 2^8 = 256
132             return (float) value / 256;
133         }
134
135         return 0;
136     }
137
138     public boolean overrides(@Nullable Message other) {
139         // If the message is a Request sent to the boiler or an Answer returned to the
140         // thermostat, and it's ID is equal to the previous message, then this is an
141         // override sent by the OpenTherm Gateway
142         return other != null && this.getID() == other.getID()
143                 && ("R".equals(this.getCode()) || "A".equals(this.getCode()));
144     }
145
146     @Override
147     public String toString() {
148         return String.format("%s - %s - %s", this.code, this.id, this.data);
149     }
150
151     public Message(String code, MessageType messageType, int id, String data) {
152         this.code = code;
153         this.messageType = messageType;
154         this.id = id;
155         this.data = data;
156     }
157
158     public static @Nullable Message parse(String message) {
159         if (messagePattern.matcher(message).matches()) {
160             // For now, only parse TBRA codes
161             String code = message.substring(0, 1);
162             MessageType messageType = getMessageType(message.substring(1, 3));
163             int id = Integer.valueOf(message.substring(3, 5), 16);
164             String data = message.substring(5);
165
166             return new Message(code, messageType, id, data);
167         }
168
169         return null;
170     }
171
172     private static MessageType getMessageType(String value) {
173         // First parse the hex value to an integer
174         int integer = Integer.parseInt(value, 16);
175
176         // Then right shift it 4 bits so that the message type bits are at the front
177         int shifted = integer >> 4;
178
179         // Then mask it with 00000111 (7), so that we only get the first 3 bits,
180         // effectively cutting off the parity bit.
181         int cutoff = shifted & 7;
182
183         switch (cutoff) {
184             case 0: // 000
185                 return MessageType.READDATA;
186             case 1: // 001
187                 return MessageType.WRITEDATA;
188             case 2: // 010
189                 return MessageType.INVALIDDATA;
190             case 3: // 011
191                 return MessageType.RESERVED;
192             case 4: // 100
193                 return MessageType.READACK;
194             case 5: // 101
195                 return MessageType.WRITEACK;
196             case 6: // 110
197                 return MessageType.DATAINVALID;
198             case 7: // 111
199             default:
200                 return MessageType.UNKNOWNDATAID;
201         }
202     }
203
204     private int parseSignedInteger(String data) {
205         // First parse the hex value to an unsigned integer value
206         int result = Integer.parseInt(data, 16);
207
208         if (data.length() == 4) {
209             // This is a two byte value, apply a bitmask of 01111111 11111111 (32767) to cut
210             // off the sign bit
211             result = result & 32767;
212
213             // Then apply a bitmask of 10000000 00000000 (32768) to check the sign bit
214             if ((result & 32768) == 32768) {
215                 // If the sign is 1000000 00000000 (32768) then it's a negative
216                 result = -32768 + result;
217             }
218         } else {
219             // This is a one byte value, apply a bitmask of 01111111 (127), to cut off the
220             // sign bit
221             result = result & 127;
222
223             // Then apply a bitmask of 10000000 (128) to check the sign bit
224             if ((result & 128) == 128) {
225                 // If the sign is 1000000 (128) then it's a negative
226                 result = -128 + result;
227             }
228         }
229
230         return result;
231     }
232
233     private String invertBinary(String value) {
234         // There is probably a better solution, but for now this works
235         String result = value;
236
237         result = result.replace('1', 'X');
238         result = result.replace('0', '1');
239         result = result.replace('X', '0');
240
241         return result;
242     }
243 }