2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.teleinfo.internal.reader.io;
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;
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;
31 * InputStream for Teleinfo {@link Frame} in serial port format.
34 * The {@link TeleinfoInputStream} class is an {@link InputStream} to decode/read Teleinfo frames.
36 * @author Nicolas SIBERIL - Initial contribution
39 public class TeleinfoInputStream extends InputStream {
41 private final Logger logger = LoggerFactory.getLogger(TeleinfoInputStream.class);
43 private BufferedReader bufferedReader;
44 private @Nullable String groupLine;
45 private boolean autoRepairInvalidADPSgroupLine;
46 private final Frame frame = new Frame();
48 public TeleinfoInputStream(final InputStream teleinfoInputStream) {
49 this(teleinfoInputStream, false);
52 public TeleinfoInputStream(final @Nullable InputStream teleinfoInputStream,
53 boolean autoRepairInvalidADPSgroupLine) {
54 if (teleinfoInputStream == null) {
55 throw new IllegalArgumentException("Teleinfo inputStream is null");
58 this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
59 this.bufferedReader = new BufferedReader(new InputStreamReader(teleinfoInputStream, StandardCharsets.US_ASCII));
65 public void close() throws IOException {
66 logger.debug("close() [start]");
67 bufferedReader.close();
69 logger.debug("close() [end]");
73 * Returns the next frame.
75 * @return the next frame or null if end of stream
76 * @throws TimeoutException
78 * @throws InvalidFrameException
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);
88 if (groupLine == null) { // end of stream
89 logger.trace("end of stream reached !");
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);
104 String labelStr = groupLineTokens[0];
105 String valueString = groupLineTokens[1];
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);
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:
126 logger.warn("Try to auto repair malformed ADPS groupLine '{}'", labelStr);
128 valueString = labelStr.substring(Label.ADPS.name().length());
130 final String error = String.format("The label '%s' is unknown", labelStr);
131 throw new InvalidFrameException(error);
135 frame.put(label, valueString);
142 public boolean isAutoRepairInvalidADPSgroupLine() {
143 return autoRepairInvalidADPSgroupLine;
146 public void setAutoRepairInvalidADPSgroupLine(boolean autoRepairInvalidADPSgroupLine) {
147 this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
151 public int read() throws IOException {
152 throw new UnsupportedOperationException("The 'read()' is not supported");
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);