2 * Copyright (c) 2010-2022 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.fineoffsetweatherstation.internal.domain;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.InputStreamReader;
18 import java.util.ArrayList;
19 import java.util.Comparator;
20 import java.util.HashMap;
21 import java.util.List;
23 import java.util.Objects;
24 import java.util.Optional;
25 import java.util.function.Predicate;
27 import org.apache.commons.csv.CSVFormat;
28 import org.apache.commons.csv.CSVParser;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants;
32 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
33 import org.openhab.core.thing.type.ChannelTypeUID;
34 import org.openhab.core.types.State;
37 * Holds all the measurands supported by the gateway.
39 * @author Andreas Berger - Initial contribution
42 public class Measurands {
44 private static final Map<Protocol, Measurands> INSTANCES = new HashMap<>();
45 private final Map<Byte, List<Parser>> parsersPerCode = new HashMap<>();
47 private Measurands(Protocol protocol) {
48 try (InputStream data = Measurands.class.getResourceAsStream("/measurands.csv")) {
50 throw new IllegalStateException("Missing measurands.csv");
52 CSVFormat csvFormat = CSVFormat.Builder.create().setHeader().setSkipHeaderRecord(true).build();
53 CSVParser.parse(new InputStreamReader(data), csvFormat).forEach(row -> {
55 byte code = Byte.valueOf(row.get("Code").replace("0x", ""), 16);
56 Optional<Integer> skip = Optional.ofNullable(row.get("Skip")).filter(Predicate.not(String::isBlank))
57 .map(Integer::valueOf);
58 int index = Optional.ofNullable(row.get("Index")).filter(Predicate.not(String::isBlank))
59 .map(Integer::valueOf).orElse(0);
62 if (skip.isPresent()) {
63 parser = new Skip(skip.get(), index);
65 String name = row.get("Name");
66 String channel = row.get("Channel");
68 ChannelTypeUID channelType = Optional.ofNullable(row.get("ChannelType"))
69 .filter(Predicate.not(String::isBlank)).map(s -> {
70 if (s.contains(":")) {
71 return new ChannelTypeUID(s);
73 return new ChannelTypeUID(FineOffsetWeatherStationBindingConstants.BINDING_ID, s);
76 String measurandString = protocol == Protocol.DEFAULT ? row.get("MeasureType_DEFAULT")
77 : Optional.ofNullable(row.get("MeasureType_" + protocol.name()))
78 .filter(Predicate.not(String::isBlank))
79 .orElseGet(() -> row.get("MeasureType_DEFAULT"));
80 parser = new MeasurandParser(channel, name, MeasureType.valueOf(measurandString), index,
84 List<Parser> parsers = parsersPerCode.computeIfAbsent(code, aByte -> new ArrayList<>());
85 // noinspection ConstantConditions
86 if (parsers != null) {
90 for (List<Parser> parsers : parsersPerCode.values()) {
91 parsers.sort(Comparator.comparing(Parser::getIndex));
93 } catch (IOException e) {
94 throw new IllegalStateException("Failed to read measurands.csv", e);
98 public static Measurands getInstance(Protocol protocol) {
99 synchronized (INSTANCES) {
100 return Objects.requireNonNull(INSTANCES.computeIfAbsent(protocol, Measurands::new));
104 private abstract static class Parser {
105 private final int index;
107 public Parser(int index) {
111 public abstract int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
112 List<MeasuredValue> result);
114 public int getIndex() {
119 private static class Skip extends Parser {
120 private final int skip;
122 public Skip(int skip, int index) {
128 public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
129 List<MeasuredValue> result) {
134 private static class MeasurandParser extends Parser {
135 private final String name;
136 private final String channelId;
137 private final MeasureType measureType;
138 private final @Nullable ChannelTypeUID channelTypeUID;
140 MeasurandParser(String channelId, String name, MeasureType measureType, int index,
141 @Nullable ChannelTypeUID channelTypeUID) {
143 this.channelId = channelId;
145 this.measureType = measureType;
146 this.channelTypeUID = channelTypeUID == null ? measureType.getChannelTypeId() : channelTypeUID;
149 public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
150 List<MeasuredValue> result) {
151 State state = measureType.toState(data, offset, context);
153 result.add(new MeasuredValue(measureType, channelId, channelTypeUID, state, name));
155 return measureType.getByteSize();
159 public int extractMeasuredValues(byte code, byte[] data, int offset, ConversionContext context,
160 List<MeasuredValue> result) {
161 List<Parser> parsers = parsersPerCode.get(code);
162 if (parsers == null) {
163 throw new IllegalArgumentException("No measurement for code 0x" + Integer.toHexString(code) + " defined");
166 for (Parser parser : parsers) {
167 subOffset += parser.extractMeasuredValues(data, offset + subOffset, context, result);