]> git.basschouten.com Git - openhab-addons.git/blob
47be517e4d66ec460e9b216c8e9bd23e8c450ced
[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.bluetooth.generic.internal;
14
15 import java.math.BigDecimal;
16 import java.math.BigInteger;
17 import java.nio.charset.StandardCharsets;
18 import java.util.Base64;
19 import java.util.Collections;
20 import java.util.Optional;
21 import java.util.stream.Collectors;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.bluetooth.gattparser.BluetoothGattParser;
26 import org.openhab.bluetooth.gattparser.FieldHolder;
27 import org.openhab.bluetooth.gattparser.GattRequest;
28 import org.openhab.bluetooth.gattparser.spec.Enumeration;
29 import org.openhab.bluetooth.gattparser.spec.Field;
30 import org.openhab.bluetooth.gattparser.spec.FieldFormat;
31 import org.openhab.bluetooth.gattparser.spec.FieldType;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.types.State;
36 import org.openhab.core.types.UnDefType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * The {@link BluetoothChannelUtils} contains utility functions used by the GattChannelHandler
42  *
43  * @author Vlad Kolotov - Initial contribution
44  * @author Connor Petty - Modified for openHAB use
45  */
46 @NonNullByDefault
47 public class BluetoothChannelUtils {
48
49     private static final Logger logger = LoggerFactory.getLogger(BluetoothChannelUtils.class);
50
51     public static String encodeFieldID(Field field) {
52         String requirements = Optional.ofNullable(field.getRequirements()).orElse(Collections.emptyList()).stream()
53                 .collect(Collectors.joining());
54         return encodeFieldName(field.getName() + requirements);
55     }
56
57     public static String encodeFieldName(String fieldName) {
58         return Base64.getEncoder().encodeToString(fieldName.getBytes(StandardCharsets.UTF_8)).replace("=", "");
59     }
60
61     public static String decodeFieldName(String encodedFieldName) {
62         return new String(Base64.getDecoder().decode(encodedFieldName), StandardCharsets.UTF_8);
63     }
64
65     public static @Nullable String getItemType(Field field) {
66         FieldFormat format = field.getFormat();
67         if (format == null) {
68             // unknown format
69             return null;
70         }
71         switch (field.getFormat().getType()) {
72             case BOOLEAN:
73                 return "Switch";
74             case UINT:
75             case SINT:
76             case FLOAT_IEE754:
77             case FLOAT_IEE11073:
78                 // BluetoothUnit unit = BluetoothUnit.findByType(field.getUnit());
79                 // if (unit != null) {
80                 // TODO
81                 // return "Number:" + unit.getUnit().getDimension();
82                 // }
83                 return "Number";
84             case UTF8S:
85             case UTF16S:
86                 return "String";
87             case STRUCT:
88                 return "String";
89             // unsupported format
90             default:
91                 return null;
92         }
93     }
94
95     public static State convert(BluetoothGattParser parser, FieldHolder holder) {
96         State state;
97         if (holder.isValueSet()) {
98             if (holder.getField().getFormat().isBoolean()) {
99                 state = OnOffType.from(Boolean.TRUE.equals(holder.getBoolean()));
100             } else {
101                 // check if we can use enumerations
102                 if (holder.getField().hasEnumerations()) {
103                     Enumeration enumeration = holder.getEnumeration();
104                     if (enumeration != null) {
105                         if (holder.getField().getFormat().isNumber()) {
106                             return new DecimalType(new BigDecimal(enumeration.getKey()));
107                         } else {
108                             return new StringType(enumeration.getKey().toString());
109                         }
110                     }
111                     // fall back to simple types
112                 }
113                 if (holder.getField().getFormat().isNumber()) {
114                     state = new DecimalType(holder.getBigDecimal());
115                 } else if (holder.getField().getFormat().isStruct()) {
116                     state = new StringType(parser.parse(holder.getBytes(), 16));
117                 } else {
118                     state = new StringType(holder.getString());
119                 }
120             }
121         } else {
122             state = UnDefType.UNDEF;
123         }
124         return state;
125     }
126
127     public static void updateHolder(BluetoothGattParser parser, GattRequest request, String fieldName, State state) {
128         Field field = request.getFieldHolder(fieldName).getField();
129         FieldType fieldType = field.getFormat().getType();
130         if (fieldType == FieldType.BOOLEAN) {
131             OnOffType onOffType = convert(state, OnOffType.class);
132             if (onOffType == null) {
133                 logger.debug("Could not convert state to OnOffType: {} : {} : {} ", request.getCharacteristicUUID(),
134                         fieldName, state);
135                 return;
136             }
137             request.setField(fieldName, onOffType == OnOffType.ON);
138             return;
139         }
140         if (field.hasEnumerations()) {
141             // check if we can use enumerations
142             Enumeration enumeration = getEnumeration(field, state);
143             if (enumeration != null) {
144                 request.setField(fieldName, enumeration);
145                 return;
146             } else {
147                 logger.debug("Could not convert state to enumeration: {} : {} : {} ", request.getCharacteristicUUID(),
148                         fieldName, state);
149             }
150             // fall back to simple types
151         }
152         switch (fieldType) {
153             case UINT:
154             case SINT: {
155                 DecimalType decimalType = convert(state, DecimalType.class);
156                 if (decimalType == null) {
157                     logger.debug("Could not convert state to DecimalType: {} : {} : {} ",
158                             request.getCharacteristicUUID(), fieldName, state);
159                     return;
160                 }
161                 request.setField(fieldName, decimalType.longValue());
162                 return;
163             }
164             case FLOAT_IEE754:
165             case FLOAT_IEE11073: {
166                 DecimalType decimalType = convert(state, DecimalType.class);
167                 if (decimalType == null) {
168                     logger.debug("Could not convert state to DecimalType: {} : {} : {} ",
169                             request.getCharacteristicUUID(), fieldName, state);
170                     return;
171                 }
172                 request.setField(fieldName, decimalType.doubleValue());
173                 return;
174             }
175             case UTF8S:
176             case UTF16S: {
177                 StringType textType = convert(state, StringType.class);
178                 if (textType == null) {
179                     logger.debug("Could not convert state to StringType: {} : {} : {} ",
180                             request.getCharacteristicUUID(), fieldName, state);
181                     return;
182                 }
183                 request.setField(fieldName, textType.toString());
184                 return;
185             }
186             case STRUCT:
187                 StringType textType = convert(state, StringType.class);
188                 if (textType == null) {
189                     logger.debug("Could not convert state to StringType: {} : {} : {} ",
190                             request.getCharacteristicUUID(), fieldName, state);
191                     return;
192                 }
193                 String text = textType.toString().trim();
194                 if (text.startsWith("[")) {
195                     request.setField(fieldName, parser.serialize(text, 16));
196                 } else {
197                     request.setField(fieldName, new BigInteger(text));
198                 }
199                 return;
200             // unsupported format
201             default:
202                 return;
203         }
204     }
205
206     private static @Nullable Enumeration getEnumeration(Field field, State state) {
207         DecimalType decimalType = convert(state, DecimalType.class);
208         if (decimalType != null) {
209             try {
210                 return field.getEnumeration(new BigInteger(decimalType.toString()));
211             } catch (NumberFormatException ignored) {
212                 // do nothing
213             }
214         }
215         return null;
216     }
217
218     private static <T extends State> @Nullable T convert(State state, Class<T> typeClass) {
219         return state.as(typeClass);
220     }
221 }