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.mqtt.generic.values;
15 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
17 import java.net.URLConnection;
18 import java.util.List;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.library.CoreItemFactory;
23 import org.openhab.core.library.types.DecimalType;
24 import org.openhab.core.library.types.PercentType;
25 import org.openhab.core.library.types.RawType;
26 import org.openhab.core.types.Command;
27 import org.openhab.core.types.CommandDescriptionBuilder;
28 import org.openhab.core.types.State;
29 import org.openhab.core.types.StateDescriptionFragmentBuilder;
30 import org.openhab.core.types.UnDefType;
33 * MQTT topics are not inherently typed.
36 * With this class users are able to map MQTT topic values to framework types,
37 * for example for numbers {@link NumberValue}, boolean values {@link OnOffValue}, percentage values
38 * {@link PercentageValue}, string values {@link TextValue} and more.
42 * This class and the encapsulated (cached) state are necessary, because MQTT can't be queried,
43 * but we still need to be able to respond to framework requests for a value.
47 * {@link #getCache()} is used to retrieve a topic state and a call to {@link #update(Command)} sets the value.
50 * @author David Graeff - Initial contribution
53 public abstract class Value {
54 protected State state = UnDefType.UNDEF;
55 protected final List<Class<? extends Command>> commandTypes;
56 private final String itemType;
58 protected Value(String itemType, List<Class<? extends Command>> commandTypes) {
59 this.itemType = itemType;
60 this.commandTypes = commandTypes;
64 * Return a list of supported command types. The order of the list is important.
66 * The framework will try to parse an incoming string into one of those command types,
67 * starting with the first and continue until it succeeds.
70 * Your {@link #update(Command)} method must accept all command types of this list.
71 * You may accept more command types. This allows you to restrict the parsing of incoming
72 * MQTT values to the listed types, but handle more user commands.
74 * A prominent example is the {@link NumberValue}, which does not return {@link PercentType},
75 * because that would interfere with {@link DecimalType} while parsing the MQTT value.
76 * It does however accept a {@link PercentType} for {@link #update(Command)}, because a
77 * linked Item could send that type of command.
79 public final List<Class<? extends Command>> getSupportedCommandTypes() {
84 * Returns the item-type (one of {@link CoreItemFactory}).
86 public final String getItemType() {
91 * Returns the current value state.
93 public final State getChannelState() {
97 public String getMQTTpublishValue(@Nullable String pattern) {
98 if (pattern == null) {
99 return state.format("%s");
101 return state.format(pattern);
105 * Returns true if this is a binary type.
107 public boolean isBinary() {
112 * If the MQTT connection is not yet initialised or no values have
113 * been received yet, the default value is {@link UnDefType#UNDEF}. To restore to the
114 * default value after a connection got lost etc, this method will be called.
116 public final void resetState() {
117 state = UnDefType.UNDEF;
121 * Updates the internal value state with the given command.
123 * @param command The command to update the internal value.
124 * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
126 public abstract void update(Command command) throws IllegalArgumentException;
129 * Returns the given command if it cannot be handled by {@link #update(Command)}
130 * or {@link #update(byte[])} and need to be posted straight to the framework instead.
131 * Returns null otherwise.
133 * @param command The command to decide about
135 public @Nullable Command isPostOnly(Command command) {
140 * Updates the internal value state with the given binary payload.
142 * @param data The binary payload to update the internal value.
143 * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
145 public void update(byte data[]) throws IllegalArgumentException {
146 String mimeType = null;
148 // URLConnection.guessContentTypeFromStream(input) is not sufficient to detect all JPEG files
149 if (data.length >= 2 && data[0] == (byte) 0xFF && data[1] == (byte) 0xD8 && data[data.length - 2] == (byte) 0xFF
150 && data[data.length - 1] == (byte) 0xD9) {
151 mimeType = "image/jpeg";
153 try (final ByteArrayInputStream input = new ByteArrayInputStream(data)) {
155 mimeType = URLConnection.guessContentTypeFromStream(input);
156 } catch (final IOException ignored) {
158 } catch (final IOException ignored) {
161 state = new RawType(data, mimeType == null ? RawType.DEFAULT_MIME_TYPE : mimeType);
165 * Return the state description fragment builder for this value state.
167 * @param readOnly True if this is a read-only value.
168 * @return A state description fragment builder
170 public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
171 return StateDescriptionFragmentBuilder.create().withReadOnly(readOnly).withPattern("%s");
175 * Return the command description builder for this value state.
177 * @return A command description builder
179 public CommandDescriptionBuilder createCommandDescription() {
180 return CommandDescriptionBuilder.create();