]> git.basschouten.com Git - openhab-addons.git/blob
3b917653b964edc7a348a29a1c41a452bc429bca
[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.teleinfo.internal.reader.io;
14
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.InputStreamReader;
19 import java.nio.charset.StandardCharsets;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.teleinfo.internal.data.Frame;
24 import org.openhab.binding.teleinfo.internal.reader.io.serialport.FrameUtil;
25 import org.openhab.binding.teleinfo.internal.reader.io.serialport.InvalidFrameException;
26 import org.openhab.binding.teleinfo.internal.reader.io.serialport.Label;
27 import org.openhab.binding.teleinfo.internal.serial.TeleinfoTicMode;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32  * InputStream for Teleinfo {@link Frame} in serial port format.
33  */
34 /**
35  * The {@link TeleinfoInputStream} class is an {@link InputStream} to decode/read Teleinfo frames.
36  *
37  * @author Nicolas SIBERIL - Initial contribution
38  */
39 @NonNullByDefault
40 public class TeleinfoInputStream extends InputStream {
41
42     private final Logger logger = LoggerFactory.getLogger(TeleinfoInputStream.class);
43
44     private BufferedReader bufferedReader;
45     private @Nullable String groupLine;
46     private boolean autoRepairInvalidADPSgroupLine;
47     private final TeleinfoTicMode ticMode;
48     private final boolean verifyChecksum;
49     private final Frame frame = new Frame();
50
51     public TeleinfoInputStream(final InputStream teleinfoInputStream, TeleinfoTicMode ticMode) {
52         this(teleinfoInputStream, false, ticMode, true);
53     }
54
55     public TeleinfoInputStream(final InputStream teleinfoInputStream, boolean autoRepairInvalidADPSgroupLine,
56             TeleinfoTicMode ticMode) {
57         this(teleinfoInputStream, autoRepairInvalidADPSgroupLine, ticMode, true);
58     }
59
60     public TeleinfoInputStream(final InputStream teleinfoInputStream, TeleinfoTicMode ticMode, boolean verifyChecksum) {
61         this(teleinfoInputStream, false, ticMode, verifyChecksum);
62     }
63
64     public TeleinfoInputStream(final @Nullable InputStream teleinfoInputStream, boolean autoRepairInvalidADPSgroupLine,
65             TeleinfoTicMode ticMode, boolean verifyChecksum) {
66         if (teleinfoInputStream == null) {
67             throw new IllegalArgumentException("Teleinfo inputStream is null");
68         }
69
70         this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
71         this.ticMode = ticMode;
72         this.verifyChecksum = verifyChecksum;
73         this.bufferedReader = new BufferedReader(new InputStreamReader(teleinfoInputStream, StandardCharsets.US_ASCII));
74
75         groupLine = null;
76     }
77
78     @Override
79     public void close() throws IOException {
80         logger.debug("close() [start]");
81         bufferedReader.close();
82         super.close();
83         logger.debug("close() [end]");
84     }
85
86     /**
87      * Returns the next frame.
88      *
89      * @return the next frame or null if end of stream
90      * @throws TimeoutException
91      * @throws IOException
92      * @throws InvalidFrameException
93      * @throws Exception
94      */
95     public synchronized @Nullable Frame readNextFrame() throws InvalidFrameException, IOException {
96         // seek the next header frame
97         while (!isHeaderFrame(groupLine)) {
98             groupLine = bufferedReader.readLine();
99             if (logger.isTraceEnabled()) {
100                 logger.trace("groupLine = {}", groupLine);
101             }
102             if (groupLine == null) { // end of stream
103                 logger.trace("end of stream reached !");
104                 return null;
105             }
106         }
107
108         frame.clear();
109         while ((groupLine = bufferedReader.readLine()) != null && !isHeaderFrame(groupLine)) {
110             logger.trace("groupLine = {}", groupLine);
111             String groupLineRef = groupLine;
112             if (groupLineRef != null) {
113                 String[] groupLineTokens = groupLineRef.split(ticMode.getSeparator());
114                 if (ticMode == TeleinfoTicMode.HISTORICAL && groupLineTokens.length != 2 && groupLineTokens.length != 3
115                         || ticMode == TeleinfoTicMode.STANDARD && groupLineTokens.length != 3
116                                 && groupLineTokens.length != 4) {
117                     final String error = String.format("The groupLine '%1$s' is incomplete", groupLineRef);
118                     throw new InvalidFrameException(error);
119                 }
120                 String labelStr = groupLineTokens[0];
121                 String valueString;
122                 String timestampString = null;
123                 switch (ticMode) {
124                     default:
125                         valueString = groupLineTokens[1];
126                         break;
127                     case STANDARD:
128                         if (groupLineTokens.length == 3) {
129                             valueString = groupLineTokens[1];
130                         } else {
131                             timestampString = groupLineTokens[1];
132                             valueString = groupLineTokens[2];
133                         }
134                         break;
135                 }
136
137                 // verify integrity (through checksum)
138                 if (verifyChecksum) {
139                     char checksum = groupLineRef.charAt(groupLineRef.length() - 1);
140                     char computedChecksum = FrameUtil
141                             .computeGroupLineChecksum(groupLineRef.substring(0, groupLineRef.length() - 2), ticMode);
142                     if (computedChecksum != checksum) {
143                         logger.trace("computedChecksum = {}", computedChecksum);
144                         logger.trace("checksum = {}", checksum);
145                         final String error = String.format(
146                                 "The groupLine '%s' is corrupted (integrity not checked). Actual checksum: '%s' / Expected checksum: '%s'",
147                                 groupLineRef, checksum, computedChecksum);
148                         throw new InvalidFrameException(error);
149                     }
150                 }
151
152                 Label label;
153                 try {
154                     label = Label.getEnum(labelStr);
155                 } catch (IllegalArgumentException e) {
156                     if (autoRepairInvalidADPSgroupLine && labelStr.startsWith(Label.ADPS.name())) {
157                         // in this hardware issue, label variable is composed by label name and value. E.g:
158                         // ADPS032
159                         logger.warn("Try to auto repair malformed ADPS groupLine '{}'", labelStr);
160                         label = Label.ADPS;
161                         valueString = labelStr.substring(Label.ADPS.name().length());
162                     } else {
163                         final String error = String.format("The label '%s' is unknown", labelStr);
164                         throw new InvalidFrameException(error);
165                     }
166                 }
167
168                 frame.put(label, valueString);
169                 if (timestampString != null) {
170                     frame.putTimestamp(label, timestampString);
171                 }
172             }
173         }
174
175         return frame;
176     }
177
178     public boolean isAutoRepairInvalidADPSgroupLine() {
179         return autoRepairInvalidADPSgroupLine;
180     }
181
182     public void setAutoRepairInvalidADPSgroupLine(boolean autoRepairInvalidADPSgroupLine) {
183         this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
184     }
185
186     @Override
187     public int read() throws IOException {
188         throw new UnsupportedOperationException("The 'read()' is not supported");
189     }
190
191     public static boolean isHeaderFrame(final @Nullable String line) {
192         // A new teleinfo trame begin with '3' and '2' bytes (END OF TEXT et START OF TEXT)
193         return (line != null && line.length() > 1 && line.codePointAt(0) == 3 && line.codePointAt(1) == 2);
194     }
195 }