]> git.basschouten.com Git - openhab-addons.git/blob
bbb7448bdaba15d97c4d38e04c504c5acdb99df5
[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.nibeheatpump.internal.protocol;
14
15 import java.nio.ByteBuffer;
16
17 import org.openhab.binding.nibeheatpump.internal.NibeHeatPumpException;
18 import org.openhab.core.util.HexUtils;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * The {@link NibeHeatPumpProtocolStates} implements Nibe heat pump protocol state machine states.
24  *
25  * @author Pauli Anttila - Initial contribution
26  */
27 public enum NibeHeatPumpProtocolStates implements NibeHeatPumpProtocolState {
28
29     WAIT_START {
30         @Override
31         public boolean process(NibeHeatPumpProtocolContext context) {
32             if (context.buffer().hasRemaining()) {
33                 byte b = context.buffer().get();
34                 if (LOGGER.isTraceEnabled()) {
35                     LOGGER.trace("Received byte: {}", String.format("%02X", b));
36                 }
37                 if (b == NibeHeatPumpProtocol.FRAME_START_CHAR_RES) {
38                     LOGGER.trace("Frame start found");
39                     context.msg().clear();
40                     context.msg().put(b);
41                     context.state(WAIT_DATA);
42                 }
43                 return true;
44             }
45             return false;
46         }
47     },
48     WAIT_DATA {
49         @Override
50         public boolean process(NibeHeatPumpProtocolContext context) {
51             if (context.buffer().hasRemaining()) {
52                 if (context.msg().position() >= 100) {
53                     LOGGER.trace("Too long message received, rewait start char");
54                     context.state(WAIT_START);
55                 } else {
56                     byte b = context.buffer().get();
57                     if (LOGGER.isTraceEnabled()) {
58                         LOGGER.trace("Received byte: {}", String.format("%02X", b));
59                     }
60                     context.msg().put(b);
61
62                     try {
63                         msgStatus status = checkNibeMessage(context.msg().asReadOnlyBuffer());
64                         switch (status) {
65                             case INVALID:
66                                 context.state(WAIT_START);
67                                 break;
68                             case VALID:
69                                 context.state(OK_MESSAGE_RECEIVED);
70                                 break;
71                             case VALID_BUT_NOT_READY:
72                                 break;
73                         }
74                     } catch (NibeHeatPumpException e) {
75                         LOGGER.trace("Error occured during parsing message: {}", e.getMessage());
76                         context.state(CHECKSUM_FAILURE);
77                     }
78                 }
79                 return true;
80             }
81             return false;
82         }
83     },
84     OK_MESSAGE_RECEIVED {
85         @Override
86         public boolean process(NibeHeatPumpProtocolContext context) {
87             context.msg().flip();
88             byte[] data = new byte[context.msg().remaining()];
89             context.msg().get(data, 0, data.length);
90             if (LOGGER.isTraceEnabled()) {
91                 LOGGER.trace("Received data (len={}): {}", data.length, HexUtils.bytesToHex(data));
92             }
93             if (NibeHeatPumpProtocol.isModbus40ReadTokenPdu(data)) {
94                 context.state(READ_TOKEN_RECEIVED);
95             } else if (NibeHeatPumpProtocol.isModbus40WriteTokenPdu(data)) {
96                 context.state(WRITE_TOKEN_RECEIVED);
97             } else {
98                 context.sendAck();
99                 context.msgReceived(data);
100                 context.state(WAIT_START);
101             }
102             return true;
103         }
104     },
105     WRITE_TOKEN_RECEIVED {
106         @Override
107         public boolean process(NibeHeatPumpProtocolContext context) {
108             LOGGER.trace("Write token received");
109             context.sendWriteMsg();
110             context.state(WAIT_START);
111             return true;
112         }
113     },
114     READ_TOKEN_RECEIVED {
115         @Override
116         public boolean process(NibeHeatPumpProtocolContext context) {
117             LOGGER.trace("Read token received");
118             context.sendReadMsg();
119             context.state(WAIT_START);
120             return true;
121         }
122     },
123     CHECKSUM_FAILURE {
124         @Override
125         public boolean process(NibeHeatPumpProtocolContext context) {
126             LOGGER.trace("CRC failure");
127             context.sendNak();
128             context.state(WAIT_START);
129             return true;
130         }
131     };
132
133     private enum msgStatus {
134         VALID,
135         VALID_BUT_NOT_READY,
136         INVALID
137     }
138
139     /*
140      * Throws NibeHeatPumpException when checksum fails
141      */
142     private static msgStatus checkNibeMessage(ByteBuffer byteBuffer) throws NibeHeatPumpException {
143         byteBuffer.flip();
144         int len = byteBuffer.remaining();
145
146         if (len >= 1) {
147             if (byteBuffer.get(0) != NibeHeatPumpProtocol.FRAME_START_CHAR_RES) {
148                 return msgStatus.INVALID;
149             }
150
151             if (len >= 6) {
152                 int datalen = byteBuffer.get(NibeHeatPumpProtocol.RES_OFFS_LEN);
153
154                 // check if all bytes received
155                 if (len < datalen + 6) {
156                     return msgStatus.VALID_BUT_NOT_READY;
157                 }
158
159                 // calculate XOR checksum
160                 byte calcChecksum = 0;
161                 for (int i = 1; i < (datalen + 5); i++) {
162                     calcChecksum ^= byteBuffer.get(i);
163                 }
164
165                 byte msgChecksum = byteBuffer.get(datalen + 5);
166
167                 if (calcChecksum != msgChecksum) {
168                     // if checksum is 0x5C (start character), heat pump seems to
169                     // send 0xC5 checksum
170                     if (calcChecksum != 0x5C && msgChecksum != 0xC5) {
171                         throw new NibeHeatPumpException(String.format(
172                                 "Checksum failure, expected checksum 0x%02X was 0x%02X", msgChecksum, calcChecksum));
173                     }
174                 }
175
176                 return msgStatus.VALID;
177             }
178         }
179
180         return msgStatus.VALID_BUT_NOT_READY;
181     }
182
183     private static final Logger LOGGER = LoggerFactory.getLogger(NibeHeatPumpProtocolStates.class);
184 }