]> git.basschouten.com Git - openhab-addons.git/blob
d520787d7bd849d395dbc7cc8d9a75dc98ae5296
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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 static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_MAX_WIND_SPEED;
16 import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_MOISTURE;
17 import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_UV_INDEX;
18
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.stream.Collectors;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
30 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
31 import org.openhab.core.thing.type.ChannelTypeUID;
32 import org.openhab.core.types.State;
33
34 /**
35  * The measurands of supported by the gateway.
36  *
37  * @author Andreas Berger - Initial contribution
38  */
39 @NonNullByDefault
40 public enum Measurand {
41
42     INTEMP("temperature-indoor", 0x01, "Indoor Temperature", MeasureType.TEMPERATURE,
43             DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_INDOOR_TEMPERATURE),
44
45     OUTTEMP("temperature-outdoor", 0x02, "Outdoor Temperature", MeasureType.TEMPERATURE,
46             DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_OUTDOOR_TEMPERATURE),
47
48     DEWPOINT("temperature-dew-point", 0x03, "Dew point", MeasureType.TEMPERATURE),
49
50     WINDCHILL("temperature-wind-chill", 0x04, "Wind chill", MeasureType.TEMPERATURE),
51
52     HEATINDEX("temperature-heat-index", 0x05, "Heat index", MeasureType.TEMPERATURE),
53
54     INHUMI("humidity-indoor", 0x06, "Indoor Humidity", MeasureType.PERCENTAGE),
55
56     OUTHUMI("humidity-outdoor", 0x07, "Outdoor Humidity", MeasureType.PERCENTAGE,
57             DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_ATMOSPHERIC_HUMIDITY),
58
59     ABSBARO("pressure-absolute", 0x08, "Absolutely pressure", MeasureType.PRESSURE),
60
61     RELBARO("pressure-relative", 0x09, "Relative pressure", MeasureType.PRESSURE,
62             DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_BAROMETRIC_PRESSURE),
63
64     WINDDIRECTION("direction-wind", 0x0A, "Wind Direction", MeasureType.DEGREE,
65             DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_WIND_DIRECTION),
66
67     WINDSPEED("speed-wind", 0x0B, "Wind Speed", MeasureType.SPEED,
68             DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_WIND_SPEED),
69
70     GUSTSPEED("speed-gust", 0x0C, "Gust Speed", MeasureType.SPEED,
71             DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_WIND_SPEED),
72
73     RAINEVENT("rain-event", 0x0D, "Rain Event", MeasureType.HEIGHT,
74             new ParserCustomization(ParserCustomizationType.ELV, MeasureType.HEIGHT_BIG)),
75
76     RAINRATE("rain-rate", 0x0E, "Rain Rate", MeasureType.HEIGHT_PER_HOUR,
77             new ParserCustomization(ParserCustomizationType.ELV, MeasureType.HEIGHT_PER_HOUR_BIG)),
78
79     RAINHOUR("rain-hour", 0x0F, "Rain hour", MeasureType.HEIGHT,
80             new ParserCustomization(ParserCustomizationType.ELV, MeasureType.HEIGHT_BIG)),
81
82     RAINDAY("rain-day", 0x10, "Rain Day", MeasureType.HEIGHT,
83             new ParserCustomization(ParserCustomizationType.ELV, MeasureType.HEIGHT_BIG),
84             new ParserCustomization(ParserCustomizationType.RAIN_READING, MeasureType.HEIGHT_BIG)),
85
86     RAINWEEK("rain-week", 0x11, "Rain Week", MeasureType.HEIGHT,
87             new ParserCustomization(ParserCustomizationType.ELV, MeasureType.HEIGHT_BIG),
88             new ParserCustomization(ParserCustomizationType.RAIN_READING, MeasureType.HEIGHT_BIG)),
89
90     RAINMONTH("rain-month", 0x12, "Rain Month", MeasureType.HEIGHT_BIG),
91
92     RAINYEAR("rain-year", 0x13, "Rain Year", MeasureType.HEIGHT_BIG),
93
94     RAINTOTALS("rain-total", 0x14, "Rain Totals", MeasureType.HEIGHT_BIG),
95
96     LIGHT("illumination", 0x15, "Light", MeasureType.LUX),
97
98     UV("irradiation-uv", 0x16, "UV", MeasureType.MILLIWATT_PER_SQUARE_METRE),
99
100     UVI("uv-index", 0x17, "UV index", MeasureType.BYTE, CHANNEL_TYPE_UV_INDEX),
101
102     TIME("time", 0x18, "Date and time", MeasureType.DATE_TIME2),
103
104     DAYLWINDMAX("wind-max-day", 0X19, "Day max wind", MeasureType.SPEED, CHANNEL_TYPE_MAX_WIND_SPEED),
105
106     TEMPX("temperature-channel", new int[] { 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21 }, "Temperature",
107             MeasureType.TEMPERATURE),
108
109     HUMIX("humidity-channel", new int[] { 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29 }, "Humidity",
110             MeasureType.PERCENTAGE),
111
112     SOILTEMPX("temperature-soil-channel",
113             new int[] { 0x2B, 0x2D, 0x2F, 0x31, 0x33, 0x35, 0x37, 0x39, 0x3B, 0x3D, 0x3F, 0x41, 0x43, 0x45, 0x47,
114                     0x49 },
115             "Soil Temperature", MeasureType.TEMPERATURE),
116
117     SOILMOISTUREX("moisture-soil-channel",
118             new int[] { 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48,
119                     0x4A },
120             "Soil Moisture", MeasureType.PERCENTAGE, CHANNEL_TYPE_MOISTURE),
121
122     // will no longer be used
123     // skip battery-level, since it is read via Command.CMD_READ_SENSOR_ID_NEW
124     LOWBATT(0x4C, new Skip(1)),
125
126     PM25_24HAVGX("air-quality-24-hour-average-channel", new int[] { 0x4D, 0x4E, 0x4F, 0x50 },
127             "PM2.5 Air Quality 24 hour average", MeasureType.PM25),
128
129     PM25_CHX("air-quality-channel", new int[] { 0x2A, 0x51, 0x52, 0x53 }, "PM2.5 Air Quality", MeasureType.PM25),
130
131     LEAK_CHX("water-leak-channel", new int[] { 0x58, 0x59, 0x5A, 0x5B }, "Leak", MeasureType.WATER_LEAK_DETECTION),
132
133     // `LIGHTNING` is the name in the spec, so we keep it here as it
134     LIGHTNING("lightning-distance", 0x60, "lightning distance 1~40KM", MeasureType.LIGHTNING_DISTANCE),
135
136     LIGHTNING_TIME("lightning-time", 0x61, "lightning happened time", MeasureType.LIGHTNING_TIME),
137
138     // `LIGHTNING_POWER` is the name in the spec, so we keep it here as it
139     LIGHTNING_POWER("lightning-counter", 0x62, "lightning counter for the day", MeasureType.LIGHTNING_COUNTER),
140
141     TF_USRX(new int[] { 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A },
142             new MeasurandParser("temperature-external-channel", "Soil or Water temperature", MeasureType.TEMPERATURE),
143             // skip battery-level, since it is read via Command.CMD_READ_SENSOR_ID_NEW
144             new Skip(1)),
145
146     ITEM_SENSOR_CO2(0x70,
147             new MeasurandParser("sensor-co2-temperature", "Temperature (CO₂-Sensor)", MeasureType.TEMPERATURE),
148             new MeasurandParser("sensor-co2-humidity", "Humidity (CO₂-Sensor)", MeasureType.PERCENTAGE),
149             new MeasurandParser("sensor-co2-pm10", "PM10 Air Quality (CO₂-Sensor)", MeasureType.PM10),
150             new MeasurandParser("sensor-co2-pm10-24-hour-average", "PM10 Air Quality 24 hour average (CO₂-Sensor)",
151                     MeasureType.PM10),
152             new MeasurandParser("sensor-co2-pm25", "PM2.5 Air Quality (CO₂-Sensor)", MeasureType.PM25),
153             new MeasurandParser("sensor-co2-pm25-24-hour-average", "PM2.5 Air Quality 24 hour average (CO₂-Sensor)",
154                     MeasureType.PM25),
155             new MeasurandParser("sensor-co2-co2", "CO₂", MeasureType.CO2),
156             new MeasurandParser("sensor-co2-co2-24-hour-average", "CO₂ 24 hour average", MeasureType.CO2),
157             // skip battery-level, since it is read via Command.CMD_READ_SENSOR_ID_NEW
158             new Skip(1)),
159
160     LEAF_WETNESS_CHX("leaf-wetness-channel", new int[] { 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79 },
161             "Leaf Moisture", MeasureType.PERCENTAGE, CHANNEL_TYPE_MOISTURE),
162
163     /**
164      * 1 Traditional rain gauge
165      * 2 Piezoelectric rain gauge
166      */
167     RAIN_PRIO(0x7a, new Skip(1)),
168
169     /**
170      * 0 = RFM433M
171      * 1 = RFM868M
172      * default = RFM915M
173      */
174     RCSATION(0x7b, new Skip(1)),
175
176     PIEZO_RAIN_RATE("piezo-rain-rate", 0x80, "Rain Rate", MeasureType.HEIGHT_PER_HOUR),
177
178     PIEZO_EVENT_RAIN("piezo-rain-event", 0x81, "Rain Event", MeasureType.HEIGHT),
179
180     PIEZO_HOURLY_RAIN("piezo-rain-hour", 0x82, "Rain hour", MeasureType.HEIGHT),
181
182     PIEZO_DAILY_RAIN("piezo-rain-day", 0x83, "Rain Day", MeasureType.HEIGHT_BIG),
183
184     PIEZO_WEEKLY_RAIN("piezo-rain-week", 0x84, "Rain Week", MeasureType.HEIGHT_BIG),
185
186     PIEZO_MONTHLY_RAIN("piezo-rain-month", 0x85, "Rain Month", MeasureType.HEIGHT_BIG),
187
188     PIEZO_YEARLY_RAIN("piezo-rain-year", 0x86, "Rain Year", MeasureType.HEIGHT_BIG),
189
190     PIEZO_GAIN10(0x87, new Skip(2 * 10)),
191
192     RST_RAIN_TIME(0x88, new Skip(3)),
193
194     ;
195
196     private static final Map<Byte, SingleChannelMeasurand> MEASURANDS = new HashMap<>();
197
198     static {
199         for (Measurand measurand : values()) {
200             for (int i = 0; i < measurand.codes.length; i++) {
201                 int code = measurand.codes[i];
202                 // if we get more than one code this measurand has multiple channels
203                 Integer channel = measurand.codes.length == 1 ? null : i + 1;
204                 MEASURANDS.put((byte) code, new SingleChannelMeasurand(measurand, channel));
205             }
206         }
207     }
208
209     private final int[] codes;
210     private final Parser[] parsers;
211
212     Measurand(String channelId, int code, String name, MeasureType measureType, ParserCustomization... customizations) {
213         this(channelId, code, name, measureType, null, customizations);
214     }
215
216     Measurand(String channelId, int[] codes, String name, MeasureType measureType,
217             ParserCustomization... customizations) {
218         this(channelId, codes, name, measureType, null, customizations);
219     }
220
221     Measurand(String channelId, int code, String name, MeasureType measureType, @Nullable ChannelTypeUID channelTypeUID,
222             ParserCustomization... customizations) {
223         this(code, new MeasurandParser(channelId, name, measureType, channelTypeUID, customizations));
224     }
225
226     Measurand(String channelId, int[] codes, String name, MeasureType measureType,
227             @Nullable ChannelTypeUID channelTypeUID, ParserCustomization... customizations) {
228         this(codes, new MeasurandParser(channelId, name, measureType, channelTypeUID, customizations));
229     }
230
231     Measurand(int code, Parser... parsers) {
232         this(new int[] { code }, parsers);
233     }
234
235     Measurand(int[] codes, Parser... parsers) {
236         this.codes = codes;
237         this.parsers = parsers;
238     }
239
240     public static @Nullable SingleChannelMeasurand getByCode(byte code) {
241         return MEASURANDS.get(code);
242     }
243
244     private int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context,
245             @Nullable ParserCustomizationType customizationType, List<MeasuredValue> result,
246             DebugDetails debugDetails) {
247         int subOffset = 0;
248         for (Parser parser : parsers) {
249             subOffset += parser.extractMeasuredValues(data, offset + subOffset, channel, context, customizationType,
250                     result, debugDetails);
251         }
252         return subOffset;
253     }
254
255     private interface Parser {
256         int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context,
257                 @Nullable ParserCustomizationType customizationType, List<MeasuredValue> result,
258                 DebugDetails debugDetails);
259     }
260
261     private static class Skip implements Parser {
262         private final int skip;
263
264         public Skip(int skip) {
265             this.skip = skip;
266         }
267
268         @Override
269         public int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context,
270                 @Nullable ParserCustomizationType customizationType, List<MeasuredValue> result,
271                 DebugDetails debugDetails) {
272             debugDetails.addDebugDetails(offset, skip, "skipped");
273             return skip;
274         }
275     }
276
277     public enum ParserCustomizationType {
278         ELV,
279         RAIN_READING
280     }
281
282     private static class ParserCustomization {
283
284         private final ParserCustomizationType type;
285         private final MeasureType measureType;
286
287         public ParserCustomization(ParserCustomizationType type, MeasureType measureType) {
288             this.type = type;
289             this.measureType = measureType;
290         }
291
292         public ParserCustomizationType getType() {
293             return type;
294         }
295
296         public MeasureType getMeasureType() {
297             return measureType;
298         }
299     }
300
301     public static class SingleChannelMeasurand {
302         private final Measurand measurand;
303         private final @Nullable Integer channel;
304
305         public SingleChannelMeasurand(Measurand measurand, @Nullable Integer channel) {
306             this.measurand = measurand;
307             this.channel = channel;
308         }
309
310         public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
311                 @Nullable ParserCustomizationType customizationType, List<MeasuredValue> result,
312                 DebugDetails debugDetails) {
313             return measurand.extractMeasuredValues(data, offset, channel, context, customizationType, result,
314                     debugDetails);
315         }
316
317         public String getDebugString() {
318             return measurand.name() + (channel == null ? "" : " channel " + channel);
319         }
320     }
321
322     private static class MeasurandParser implements Parser {
323         private final String name;
324         private final String channelPrefix;
325         private final MeasureType measureType;
326
327         private final @Nullable Map<ParserCustomizationType, ParserCustomization> customizations;
328         private final @Nullable ChannelTypeUID channelTypeUID;
329
330         MeasurandParser(String channelPrefix, String name, MeasureType measureType,
331                 ParserCustomization... customizations) {
332             this(channelPrefix, name, measureType, null, customizations);
333         }
334
335         MeasurandParser(String channelPrefix, String name, MeasureType measureType,
336                 @Nullable ChannelTypeUID channelTypeUID, ParserCustomization... customizations) {
337             this.channelPrefix = channelPrefix;
338             this.name = name;
339             this.measureType = measureType;
340             this.channelTypeUID = channelTypeUID;
341             if (customizations.length == 0) {
342                 this.customizations = null;
343             } else {
344                 this.customizations = Collections.unmodifiableMap(
345                         Arrays.stream(customizations).collect(Collectors.toMap(ParserCustomization::getType,
346                                 customization -> customization, (a, b) -> b, HashMap::new)));
347             }
348         }
349
350         @Override
351         public int extractMeasuredValues(byte[] data, int offset, @Nullable Integer channel, ConversionContext context,
352                 @Nullable ParserCustomizationType customizationType, List<MeasuredValue> result,
353                 DebugDetails debugDetails) {
354             MeasureType measureType = getMeasureType(customizationType);
355             State state = measureType.toState(data, offset, context);
356             if (state != null) {
357                 debugDetails.addDebugDetails(offset, measureType.getByteSize(),
358                         measureType.name() + ": " + state.toFullString());
359                 ChannelTypeUID channelType = channelTypeUID == null ? measureType.getChannelTypeId() : channelTypeUID;
360                 result.add(new MeasuredValue(measureType, channelPrefix, channel, channelType, state, name));
361             } else {
362                 debugDetails.addDebugDetails(offset, measureType.getByteSize(), measureType.name() + ": null");
363             }
364             return measureType.getByteSize();
365         }
366
367         public MeasureType getMeasureType(@Nullable ParserCustomizationType customizationType) {
368             if (customizationType == null) {
369                 return measureType;
370             }
371             return Optional.ofNullable(customizations).map(m -> m.get(customizationType))
372                     .map(ParserCustomization::getMeasureType).orElse(measureType);
373         }
374     }
375 }