2 * Copyright (c) 2010-2023 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.service;
15 import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt16;
16 import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt32;
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;
24 import java.util.function.Supplier;
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;
42 * Class to Convert the protocol data
44 * @author Andreas Berger - Initial contribution
47 public class FineOffsetDataParser {
48 private final Logger logger = LoggerFactory.getLogger(FineOffsetDataParser.class);
49 private final Protocol protocol;
51 public FineOffsetDataParser(Protocol protocol) {
52 this.protocol = protocol;
55 public @Nullable String getFirmwareVersion(byte[] data) {
56 if (data.length > 0) {
57 return new String(data, 5, data[4]);
62 public Map<SensorGatewayBinding, SensorDevice> getRegisteredSensors(byte[] data,
63 Supplier<@Nullable Boolean> isUseWh24) {
65 * Pos | Length | Description
66 * -------------------------------------------------
67 * 0 | 2 | fixed header (0xffff)
68 * 2 | 1 | command (0x3c)
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
79 Map<SensorGatewayBinding, SensorDevice> result = new HashMap<>();
80 var len = toUInt16(data, 3);
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]);
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;
98 if (sensorGatewayBinding == null) {
99 logger.debug("too many sensor candidates for (id={}) and index {}: {}", id, data[idx],
105 logger.trace("sensor {} = disabled", sensorGatewayBinding);
108 logger.trace("sensor {} = registering", sensorGatewayBinding);
112 BatteryStatus batteryStatus = sensorGatewayBinding.getBatteryStatus(data[idx + 5]);
113 int signal = Utils.toUInt8(data[idx + 6]);
115 result.put(sensorGatewayBinding, new SensorDevice(id, sensorGatewayBinding, batteryStatus, signal));
120 public @Nullable SystemInfo fetchSystemInfo(byte[] data) {
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
129 // 10 - time zone index (?)
130 // 11 - DST 0-1 - false/true
131 // 12 - 0x?? - checksum
132 Integer frequency = null;
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);
155 List<MeasuredValue> getMeasuredValues(byte[] data, ConversionContext context, DebugDetails debugDetails) {
157 * Pos| Length | Description
158 * -------------------------------------------------
159 * 0 | 2 | fixed header (0xffff)
160 * 2 | 1 | command (0x27)
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 * -------------------------------------------------
170 * -------------------------------------------------
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");
179 return readMeasuredValues(data, idx, context, protocol.getParserCustomizationType(), debugDetails);
182 List<MeasuredValue> getRainData(byte[] data, ConversionContext context, DebugDetails debugDetails) {
183 return readMeasuredValues(data, 5, context, Measurand.ParserCustomizationType.RAIN_READING, debugDetails);
186 private List<MeasuredValue> readMeasuredValues(byte[] data, int idx, ConversionContext context,
187 Measurand.@Nullable ParserCustomizationType protocol, DebugDetails debugDetails) {
188 var size = toUInt16(data, 3);
190 List<MeasuredValue> result = new ArrayList<>();
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");
199 debugDetails.addDebugDetails(idx - 1, 1, "measurand " + measurand.getDebugString());
201 idx += measurand.extractMeasuredValues(data, idx, context, protocol, result, debugDetails);