]> git.basschouten.com Git - openhab-addons.git/blob
d19a56539af5a4b050758ba0555626e886ed2932
[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.bluetooth.daikinmadoka.internal.model;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.ByteArrayOutputStream;
17 import java.io.DataOutputStream;
18 import java.io.IOException;
19 import java.nio.ByteBuffer;
20 import java.util.Arrays;
21 import java.util.HashMap;
22 import java.util.Map;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.BRC1HCommand;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * This class represents a message transmitted or received from the BRC1H controller as a serial protocol
32  *
33  * @author Benjamin Lafois - Initial contribution
34  */
35 @NonNullByDefault
36 public class MadokaMessage {
37
38     private static final Logger logger = LoggerFactory.getLogger(MadokaMessage.class);
39
40     private int messageId;
41     private final Map<Integer, MadokaValue> values;
42
43     private byte @Nullable [] rawMessage;
44
45     private MadokaMessage() {
46         values = new HashMap<>();
47     }
48
49     public static byte[][] createRequest(BRC1HCommand command, MadokaValue... parameters) {
50         try {
51             ByteArrayOutputStream output = new ByteArrayOutputStream();
52             DataOutputStream request = new DataOutputStream(output);
53
54             // Chunk ID
55             // request.writeByte(0);
56
57             // Message Length - Computed in the end - left at 0 for now
58             request.writeByte(0);
59
60             // Command ID, coded on 3 bytes
61             request.writeByte(0);
62             request.writeShort(command.getCommandId());
63
64             if (parameters.length == 0) {
65                 request.writeByte(0);
66                 request.writeByte(0);
67             } else {
68                 for (MadokaValue mv : parameters) {
69                     request.writeByte(mv.getId());
70                     request.writeByte(mv.getSize());
71                     request.write(mv.getRawValue());
72                 }
73             }
74
75             // Finally, compute array size
76             byte[] payload = output.toByteArray();
77             payload[0] = (byte) (payload.length);
78
79             // Now, split in chunks
80             byte[][] chunks = new byte[(int) Math.ceil(payload.length / 19.)][0];
81
82             ByteArrayInputStream left = new ByteArrayInputStream(payload);
83             int chunkId = 0;
84             while (left.available() > 0) {
85                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
86                 DataOutputStream chunk = new DataOutputStream(bos);
87                 chunk.writeByte(chunkId);
88                 chunk.write(left.readNBytes(19));
89
90                 chunk.flush();
91                 chunks[chunkId++] = bos.toByteArray();
92             }
93
94             return chunks;
95         } catch (IOException e) {
96             logger.info("Error while building request", e);
97             throw new RuntimeException(e);
98         }
99     }
100
101     public static MadokaMessage parse(byte[] msg) throws MadokaParsingException {
102         // Msg format (bytes):
103         // <Msg Length> <msg id> <msg id> <msg id> ...
104         // So MINIMAL length is 4, to cover the message length + message ID
105         if (msg.length < 4) {
106             throw new MadokaParsingException("Message received is too short to be parsed.");
107         }
108         if (msg[0] != msg.length) {
109             throw new MadokaParsingException("Message size is not valid (different from byte[0]).");
110         }
111
112         MadokaMessage m = new MadokaMessage();
113         m.setRawMessage(msg);
114         m.messageId = ByteBuffer.wrap(msg, 2, 2).getShort();
115
116         MadokaValue mv = null;
117
118         // Starting here, we are not on the safe side with previous msg.length check
119         for (int i = 4; i < msg.length;) {
120             if ((i + 1) >= msg.length) {
121                 throw new MadokaParsingException("Truncated message detected while parsing response value header");
122             }
123
124             mv = new MadokaValue();
125             mv.setId(msg[i]);
126
127             if (Byte.toUnsignedInt(msg[i + 1]) == 0xff) {
128                 // Specific case - msg length 0xFF. See GetOperationHousCommand
129                 mv.setSize(0);
130             } else {
131                 mv.setSize(Byte.toUnsignedInt(msg[i + 1]));
132             }
133
134             if ((i + 1 + mv.getSize()) >= msg.length) {
135                 throw new MadokaParsingException("Truncated message detected while parsing response value content");
136             }
137
138             mv.setRawValue(Arrays.copyOfRange(msg, i + 2, i + 2 + mv.getSize()));
139
140             i += 2 + mv.getSize();
141
142             m.values.put(mv.getId(), mv);
143         }
144
145         return m;
146     }
147
148     private void setRawMessage(byte[] rawMessage) {
149         this.rawMessage = rawMessage;
150     }
151
152     public byte @Nullable [] getRawMessage() {
153         return this.rawMessage;
154     }
155
156     public int getMessageId() {
157         return messageId;
158     }
159
160     public Map<Integer, MadokaValue> getValues() {
161         return values;
162     }
163
164     @Override
165     public String toString() {
166         StringBuilder sb = new StringBuilder();
167
168         sb.append(String.format("{ messageId: %d, values: [", this.messageId));
169
170         for (Map.Entry<Integer, MadokaValue> entry : values.entrySet()) {
171             sb.append(String.format(" { valueId: %d, valueSize: %d },", entry.getKey(), entry.getValue().getSize()));
172         }
173
174         sb.append("] }");
175         return sb.toString();
176     }
177 }