]> git.basschouten.com Git - openhab-addons.git/blob
1b5fec503fc580722377ca77def68a861e64d664
[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 IOException
91      * @throws InvalidFrameException
92      */
93     public synchronized @Nullable Frame readNextFrame() throws InvalidFrameException, IOException {
94         // seek the next header frame
95         while (!isHeaderFrame(groupLine)) {
96             groupLine = bufferedReader.readLine();
97             if (logger.isTraceEnabled()) {
98                 logger.trace("groupLine = {}", groupLine);
99             }
100             if (groupLine == null) { // end of stream
101                 logger.trace("end of stream reached !");
102                 return null;
103             }
104         }
105
106         frame.clear();
107         while ((groupLine = bufferedReader.readLine()) != null && !isHeaderFrame(groupLine)) {
108             logger.trace("groupLine = {}", groupLine);
109             String groupLineRef = groupLine;
110             if (groupLineRef != null) {
111                 String[] groupLineTokens = groupLineRef.split(ticMode.getSeparator());
112                 if (ticMode == TeleinfoTicMode.HISTORICAL && groupLineTokens.length != 2 && groupLineTokens.length != 3
113                         || ticMode == TeleinfoTicMode.STANDARD && groupLineTokens.length != 3
114                                 && groupLineTokens.length != 4) {
115                     final String error = String.format("The groupLine '%1$s' is incomplete", groupLineRef);
116                     throw new InvalidFrameException(error);
117                 }
118                 String labelStr = groupLineTokens[0];
119                 String valueString;
120                 String timestampString = null;
121                 switch (ticMode) {
122                     default:
123                         valueString = groupLineTokens[1];
124                         break;
125                     case STANDARD:
126                         if (groupLineTokens.length == 3) {
127                             valueString = groupLineTokens[1];
128                         } else {
129                             timestampString = groupLineTokens[1];
130                             valueString = groupLineTokens[2];
131                         }
132                         break;
133                 }
134
135                 // verify integrity (through checksum)
136                 if (verifyChecksum) {
137                     char checksum = groupLineRef.charAt(groupLineRef.length() - 1);
138                     char computedChecksum = FrameUtil
139                             .computeGroupLineChecksum(groupLineRef.substring(0, groupLineRef.length() - 2), ticMode);
140                     if (computedChecksum != checksum) {
141                         logger.trace("computedChecksum = {}", computedChecksum);
142                         logger.trace("checksum = {}", checksum);
143                         final String error = String.format(
144                                 "The groupLine '%s' is corrupted (integrity not checked). Actual checksum: '%s' / Expected checksum: '%s'",
145                                 groupLineRef, checksum, computedChecksum);
146                         throw new InvalidFrameException(error);
147                     }
148                 }
149
150                 Label label;
151                 try {
152                     label = Label.getEnum(labelStr);
153                 } catch (IllegalArgumentException e) {
154                     if (autoRepairInvalidADPSgroupLine && labelStr.startsWith(Label.ADPS.name())) {
155                         // in this hardware issue, label variable is composed by label name and value. E.g:
156                         // ADPS032
157                         logger.warn("Try to auto repair malformed ADPS groupLine '{}'", labelStr);
158                         label = Label.ADPS;
159                         valueString = labelStr.substring(Label.ADPS.name().length());
160                     } else {
161                         final String error = String.format("The label '%s' is unknown", labelStr);
162                         throw new InvalidFrameException(error);
163                     }
164                 }
165
166                 frame.put(label, valueString);
167                 if (timestampString != null) {
168                     frame.putTimestamp(label, timestampString);
169                 }
170             }
171         }
172
173         return frame;
174     }
175
176     public boolean isAutoRepairInvalidADPSgroupLine() {
177         return autoRepairInvalidADPSgroupLine;
178     }
179
180     public void setAutoRepairInvalidADPSgroupLine(boolean autoRepairInvalidADPSgroupLine) {
181         this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
182     }
183
184     @Override
185     public int read() throws IOException {
186         throw new UnsupportedOperationException("The 'read()' is not supported");
187     }
188
189     public static boolean isHeaderFrame(final @Nullable String line) {
190         // A new teleinfo trame begin with '3' and '2' bytes (END OF TEXT et START OF TEXT)
191         return (line != null && line.length() > 1 && line.codePointAt(0) == 3 && line.codePointAt(1) == 2);
192     }
193 }