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