]> git.basschouten.com Git - openhab-addons.git/blob
270675054f9183d800e626c2d668bf6473f4d596
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * InputStream for Teleinfo {@link Frame} in serial port format.
32  */
33 /**
34  * The {@link TeleinfoInputStream} class is an {@link InputStream} to decode/read Teleinfo frames.
35  *
36  * @author Nicolas SIBERIL - Initial contribution
37  */
38 @NonNullByDefault
39 public class TeleinfoInputStream extends InputStream {
40
41     private final Logger logger = LoggerFactory.getLogger(TeleinfoInputStream.class);
42
43     private BufferedReader bufferedReader;
44     private @Nullable String groupLine;
45     private boolean autoRepairInvalidADPSgroupLine;
46     private final Frame frame = new Frame();
47
48     public TeleinfoInputStream(final InputStream teleinfoInputStream) {
49         this(teleinfoInputStream, false);
50     }
51
52     public TeleinfoInputStream(final @Nullable InputStream teleinfoInputStream,
53             boolean autoRepairInvalidADPSgroupLine) {
54         if (teleinfoInputStream == null) {
55             throw new IllegalArgumentException("Teleinfo inputStream is null");
56         }
57
58         this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
59         this.bufferedReader = new BufferedReader(new InputStreamReader(teleinfoInputStream, StandardCharsets.US_ASCII));
60
61         groupLine = null;
62     }
63
64     @Override
65     public void close() throws IOException {
66         logger.debug("close() [start]");
67         bufferedReader.close();
68         super.close();
69         logger.debug("close() [end]");
70     }
71
72     /**
73      * Returns the next frame.
74      *
75      * @return the next frame or null if end of stream
76      * @throws TimeoutException
77      * @throws IOException
78      * @throws InvalidFrameException
79      * @throws Exception
80      */
81     public synchronized @Nullable Frame readNextFrame() throws InvalidFrameException, IOException {
82         // seek the next header frame
83         while (!isHeaderFrame(groupLine)) {
84             groupLine = bufferedReader.readLine();
85             if (logger.isTraceEnabled()) {
86                 logger.trace("groupLine = {}", groupLine);
87             }
88             if (groupLine == null) { // end of stream
89                 logger.trace("end of stream reached !");
90                 return null;
91             }
92         }
93
94         frame.clear();
95         while ((groupLine = bufferedReader.readLine()) != null && !isHeaderFrame(groupLine)) {
96             logger.trace("groupLine = {}", groupLine);
97             String groupLineRef = groupLine;
98             if (groupLineRef != null) {
99                 String[] groupLineTokens = groupLineRef.split("\\s");
100                 if (groupLineTokens.length != 2 && groupLineTokens.length != 3) {
101                     final String error = String.format("The groupLine '%1$s' is incomplete", groupLineRef);
102                     throw new InvalidFrameException(error);
103                 }
104                 String labelStr = groupLineTokens[0];
105                 String valueString = groupLineTokens[1];
106
107                 // verify integrity (through checksum)
108                 char checksum = (groupLineTokens.length == 3 ? groupLineTokens[2].charAt(0) : ' ');
109                 char computedChecksum = FrameUtil.computeGroupLineChecksum(labelStr, valueString);
110                 if (computedChecksum != checksum) {
111                     logger.trace("computedChecksum = {}", computedChecksum);
112                     logger.trace("checksum = {}", checksum);
113                     final String error = String.format(
114                             "The groupLine '%s' is corrupted (integrity not checked). Actual checksum: '%s' / Expected checksum: '%s'",
115                             groupLineRef, checksum, computedChecksum);
116                     throw new InvalidFrameException(error);
117                 }
118
119                 Label label;
120                 try {
121                     label = Label.valueOf(labelStr);
122                 } catch (IllegalArgumentException e) {
123                     if (autoRepairInvalidADPSgroupLine && labelStr.startsWith(Label.ADPS.name())) {
124                         // in this hardware issue, label variable is composed by label name and value. E.g:
125                         // ADPS032
126                         logger.warn("Try to auto repair malformed ADPS groupLine '{}'", labelStr);
127                         label = Label.ADPS;
128                         valueString = labelStr.substring(Label.ADPS.name().length());
129                     } else {
130                         final String error = String.format("The label '%s' is unknown", labelStr);
131                         throw new InvalidFrameException(error);
132                     }
133                 }
134
135                 frame.put(label, valueString);
136             }
137         }
138
139         return frame;
140     }
141
142     public boolean isAutoRepairInvalidADPSgroupLine() {
143         return autoRepairInvalidADPSgroupLine;
144     }
145
146     public void setAutoRepairInvalidADPSgroupLine(boolean autoRepairInvalidADPSgroupLine) {
147         this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
148     }
149
150     @Override
151     public int read() throws IOException {
152         throw new UnsupportedOperationException("The 'read()' is not supported");
153     }
154
155     private boolean isHeaderFrame(final @Nullable String line) {
156         // A new teleinfo trame begin with '3' and '2' bytes (END OF TEXT et START OF TEXT)
157         return (line != null && line.length() > 1 && line.codePointAt(0) == 3 && line.codePointAt(1) == 2);
158     }
159 }