2 * Copyright (c) 2010-2024 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.bluetooth.generic.internal;
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;
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;
41 * The {@link BluetoothChannelUtils} contains utility functions used by the GattChannelHandler
43 * @author Vlad Kolotov - Initial contribution
44 * @author Connor Petty - Modified for openHAB use
47 public class BluetoothChannelUtils {
49 private static final Logger logger = LoggerFactory.getLogger(BluetoothChannelUtils.class);
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);
57 public static String encodeFieldName(String fieldName) {
58 return Base64.getEncoder().encodeToString(fieldName.getBytes(StandardCharsets.UTF_8)).replace("=", "");
61 public static String decodeFieldName(String encodedFieldName) {
62 return new String(Base64.getDecoder().decode(encodedFieldName), StandardCharsets.UTF_8);
65 public static @Nullable String getItemType(Field field) {
66 FieldFormat format = field.getFormat();
71 switch (field.getFormat().getType()) {
78 // BluetoothUnit unit = BluetoothUnit.findByType(field.getUnit());
79 // if (unit != null) {
81 // return "Number:" + unit.getUnit().getDimension();
95 public static State convert(BluetoothGattParser parser, FieldHolder holder) {
97 if (holder.isValueSet()) {
98 if (holder.getField().getFormat().isBoolean()) {
99 state = OnOffType.from(Boolean.TRUE.equals(holder.getBoolean()));
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()));
108 return new StringType(enumeration.getKey().toString());
111 // fall back to simple types
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));
118 state = new StringType(holder.getString());
122 state = UnDefType.UNDEF;
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(),
137 request.setField(fieldName, onOffType == OnOffType.ON);
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);
147 logger.debug("Could not convert state to enumeration: {} : {} : {} ", request.getCharacteristicUUID(),
150 // fall back to simple types
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);
161 request.setField(fieldName, decimalType.longValue());
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);
172 request.setField(fieldName, decimalType.doubleValue());
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);
183 request.setField(fieldName, textType.toString());
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);
193 String text = textType.toString().trim();
194 if (text.startsWith("[")) {
195 request.setField(fieldName, parser.serialize(text, 16));
197 request.setField(fieldName, new BigInteger(text));
200 // unsupported format
206 private static @Nullable Enumeration getEnumeration(Field field, State state) {
207 DecimalType decimalType = convert(state, DecimalType.class);
208 if (decimalType != null) {
210 return field.getEnumeration(new BigInteger(decimalType.toString()));
211 } catch (NumberFormatException ignored) {
218 private static <T extends State> @Nullable T convert(State state, Class<T> typeClass) {
219 return state.as(typeClass);