]> git.basschouten.com Git - openhab-addons.git/blob
27658feb4d2e64d4134dc95bd76e93d4e0cacc19
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.fineoffsetweatherstation.internal.domain;
14
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;
22 import java.util.Map;
23 import java.util.Objects;
24 import java.util.Optional;
25 import java.util.function.Predicate;
26
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;
35
36 /**
37  * Holds all the measurands supported by the gateway.
38  *
39  * @author Andreas Berger - Initial contribution
40  */
41 @NonNullByDefault
42 public class Measurands {
43
44     private static final Map<Protocol, Measurands> INSTANCES = new HashMap<>();
45     private final Map<Byte, List<Parser>> parsersPerCode = new HashMap<>();
46
47     private Measurands(Protocol protocol) {
48         try (InputStream data = Measurands.class.getResourceAsStream("/measurands.csv")) {
49             if (data == null) {
50                 throw new IllegalStateException("Missing measurands.csv");
51             }
52             CSVFormat csvFormat = CSVFormat.Builder.create().setHeader().setSkipHeaderRecord(true).build();
53             CSVParser.parse(new InputStreamReader(data), csvFormat).forEach(row -> {
54
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);
60
61                 Parser parser;
62                 if (skip.isPresent()) {
63                     parser = new Skip(skip.get(), index);
64                 } else {
65                     String name = row.get("Name");
66                     String channel = row.get("Channel");
67
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);
72                                 } else {
73                                     return new ChannelTypeUID(FineOffsetWeatherStationBindingConstants.BINDING_ID, s);
74                                 }
75                             }).orElse(null);
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,
81                             channelType);
82                 }
83
84                 List<Parser> parsers = parsersPerCode.computeIfAbsent(code, aByte -> new ArrayList<>());
85                 // noinspection ConstantConditions
86                 if (parsers != null) {
87                     parsers.add(parser);
88                 }
89             });
90             for (List<Parser> parsers : parsersPerCode.values()) {
91                 parsers.sort(Comparator.comparing(Parser::getIndex));
92             }
93         } catch (IOException e) {
94             throw new IllegalStateException("Failed to read measurands.csv", e);
95         }
96     }
97
98     public static Measurands getInstance(Protocol protocol) {
99         synchronized (INSTANCES) {
100             return Objects.requireNonNull(INSTANCES.computeIfAbsent(protocol, Measurands::new));
101         }
102     }
103
104     private abstract static class Parser {
105         private final int index;
106
107         public Parser(int index) {
108             this.index = index;
109         }
110
111         public abstract int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
112                 List<MeasuredValue> result);
113
114         public int getIndex() {
115             return index;
116         }
117     }
118
119     private static class Skip extends Parser {
120         private final int skip;
121
122         public Skip(int skip, int index) {
123             super(index);
124             this.skip = skip;
125         }
126
127         @Override
128         public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
129                 List<MeasuredValue> result) {
130             return skip;
131         }
132     }
133
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;
139
140         MeasurandParser(String channelId, String name, MeasureType measureType, int index,
141                 @Nullable ChannelTypeUID channelTypeUID) {
142             super(index);
143             this.channelId = channelId;
144             this.name = name;
145             this.measureType = measureType;
146             this.channelTypeUID = channelTypeUID == null ? measureType.getChannelTypeId() : channelTypeUID;
147         }
148
149         public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
150                 List<MeasuredValue> result) {
151             State state = measureType.toState(data, offset, context);
152             if (state != null) {
153                 result.add(new MeasuredValue(measureType, channelId, channelTypeUID, state, name));
154             }
155             return measureType.getByteSize();
156         }
157     }
158
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");
164         }
165         int subOffset = 0;
166         for (Parser parser : parsers) {
167             subOffset += parser.extractMeasuredValues(data, offset + subOffset, context, result);
168         }
169         return subOffset;
170     }
171 }