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