]> git.basschouten.com Git - openhab-addons.git/blob
c1ce0fd76ef9f654371a143a9e410e1fff867b9c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.service;
14
15 import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt16;
16 import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt32;
17
18 import java.time.LocalDateTime;
19 import java.time.ZoneOffset;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.function.Supplier;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
29 import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
30 import org.openhab.binding.fineoffsetweatherstation.internal.domain.DebugDetails;
31 import org.openhab.binding.fineoffsetweatherstation.internal.domain.Measurand;
32 import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol;
33 import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
34 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus;
35 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
36 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
37 import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Class to Convert the protocol data
43  *
44  * @author Andreas Berger - Initial contribution
45  */
46 @NonNullByDefault
47 public class FineOffsetDataParser {
48     private final Logger logger = LoggerFactory.getLogger(FineOffsetDataParser.class);
49     private final Protocol protocol;
50
51     public FineOffsetDataParser(Protocol protocol) {
52         this.protocol = protocol;
53     }
54
55     public @Nullable String getFirmwareVersion(byte[] data) {
56         if (data.length > 0) {
57             return new String(data, 5, data[4]);
58         }
59         return null;
60     }
61
62     public Map<SensorGatewayBinding, SensorDevice> getRegisteredSensors(byte[] data,
63             Supplier<@Nullable Boolean> isUseWh24) {
64         /*
65          * Pos | Length | Description
66          * -------------------------------------------------
67          * 0 | 2 | fixed header (0xffff)
68          * 2 | 1 | command (0x3c)
69          * 3 | 2 | size
70          * -------------------------------------------------
71          * (n * 7) + 5 | 1 | index of sensor n
72          * (n * 7) + 6 | 4 | id of sensor n
73          * (n * 7) + 10 | 1 | battery status of sensor n
74          * (n * 7) + 11 | 1 | signal of sensor n
75          * -------------------------------------------------
76          * (n * 7) + 12 | 1 | checksum
77          */
78
79         Map<SensorGatewayBinding, SensorDevice> result = new HashMap<>();
80         var len = toUInt16(data, 3);
81         int entry = 0;
82         int entrySize = 7;
83         while (entry * entrySize + 11 <= len) {
84             int idx = entry++ * entrySize + 5;
85             int id = toUInt32(data, idx + 1);
86             List<SensorGatewayBinding> sensorCandidates = SensorGatewayBinding.forIndex(data[idx]);
87             if (sensorCandidates == null || sensorCandidates.isEmpty()) {
88                 logger.debug("unknown sensor (id={}) for index {}", id, data[idx]);
89                 continue;
90             }
91             SensorGatewayBinding sensorGatewayBinding = null;
92             if (sensorCandidates.size() == 1) {
93                 sensorGatewayBinding = sensorCandidates.get(0);
94             } else if (sensorCandidates.size() == 2 && data[idx] == 0) {
95                 sensorGatewayBinding = Boolean.TRUE.equals(isUseWh24.get()) ? SensorGatewayBinding.WH24
96                         : SensorGatewayBinding.WH65;
97             }
98             if (sensorGatewayBinding == null) {
99                 logger.debug("too many sensor candidates for (id={}) and index {}: {}", id, data[idx],
100                         sensorCandidates);
101                 continue;
102             }
103             switch (id) {
104                 case 0xFFFFFFFE:
105                     logger.trace("sensor {} = disabled", sensorGatewayBinding);
106                     continue;
107                 case 0xFFFFFFFF:
108                     logger.trace("sensor {} = registering", sensorGatewayBinding);
109                     continue;
110             }
111
112             BatteryStatus batteryStatus = sensorGatewayBinding.getBatteryStatus(data[idx + 5]);
113             int signal = Utils.toUInt8(data[idx + 6]);
114
115             result.put(sensorGatewayBinding, new SensorDevice(id, sensorGatewayBinding, batteryStatus, signal));
116         }
117         return result;
118     }
119
120     public @Nullable SystemInfo fetchSystemInfo(byte[] data) {
121         // expected response
122         // 0 - 0xff - header
123         // 1 - 0xff - header
124         // 2 - 0x30 - system info
125         // 3 - 0x?? - size of response
126         // 4 - frequency - 0=433, 1=868MHz, 2=915MHz, 3=920MHz
127         // 5 - sensor type - 0=WH24, 1=WH65
128         // 6-9 - UTC time
129         // 10 - time zone index (?)
130         // 11 - DST 0-1 - false/true
131         // 12 - 0x?? - checksum
132         Integer frequency = null;
133         switch (data[4]) {
134             case 0:
135                 frequency = 433;
136                 break;
137             case 1:
138                 frequency = 868;
139                 break;
140             case 2:
141                 frequency = 915;
142                 break;
143             case 3:
144                 frequency = 920;
145                 break;
146
147         }
148         boolean useWh24 = data[5] == 0;
149         var unix = toUInt32(data, 6);
150         var date = LocalDateTime.ofEpochSecond(unix, 0, ZoneOffset.UTC);
151         var dst = data[11] != 0;
152         return new SystemInfo(frequency, date, dst, useWh24);
153     }
154
155     List<MeasuredValue> getMeasuredValues(byte[] data, ConversionContext context, DebugDetails debugDetails) {
156         /*
157          * Pos| Length | Description
158          * -------------------------------------------------
159          * 0 | 2 | fixed header (0xffff)
160          * 2 | 1 | command (0x27)
161          * 3 | 2 | size
162          * -------------------------------------------------
163          * 5 | 1 | code of item (item defines n)
164          * 6 | n | value of item
165          * -------------------------------------------------
166          * 6 + n | 1 | code of item (item defines m)
167          * 7 + n | m | value of item
168          * -------------------------------------------------
169          * ...
170          * -------------------------------------------------
171          *
172          * | 1 | checksum
173          */
174         var idx = 5;
175         if (protocol == Protocol.ELV) {
176             idx++; // at index 5 there is an additional Byte being set to 0x04
177             debugDetails.addDebugDetails(5, 1, "ELV extra byte");
178         }
179         return readMeasuredValues(data, idx, context, protocol.getParserCustomizationType(), debugDetails);
180     }
181
182     List<MeasuredValue> getRainData(byte[] data, ConversionContext context, DebugDetails debugDetails) {
183         return readMeasuredValues(data, 5, context, Measurand.ParserCustomizationType.RAIN_READING, debugDetails);
184     }
185
186     private List<MeasuredValue> readMeasuredValues(byte[] data, int idx, ConversionContext context,
187             Measurand.@Nullable ParserCustomizationType protocol, DebugDetails debugDetails) {
188         var size = toUInt16(data, 3);
189
190         List<MeasuredValue> result = new ArrayList<>();
191         while (idx < size) {
192             byte code = data[idx++];
193             Measurand.SingleChannelMeasurand measurand = Measurand.getByCode(code);
194             if (measurand == null) {
195                 logger.warn("failed to get measurand 0x{}", Integer.toHexString(code));
196                 debugDetails.addDebugDetails(idx - 1, 1, "unknown measurand");
197                 return result;
198             } else {
199                 debugDetails.addDebugDetails(idx - 1, 1, "measurand " + measurand.getDebugString());
200             }
201             idx += measurand.extractMeasuredValues(data, idx, context, protocol, result, debugDetails);
202         }
203         return result;
204     }
205 }