]> git.basschouten.com Git - openhab-addons.git/blob
70682b82c512d14abfa34961f175ded65fd3eac2
[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.mqtt.generic.values;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
17 import java.net.URLConnection;
18 import java.util.List;
19
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;
31
32 /**
33  * MQTT topics are not inherently typed.
34  *
35  * <p>
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.
39  * </p>
40  *
41  * <p>
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.
44  * </p>
45  *
46  * <p>
47  * {@link #getCache()} is used to retrieve a topic state and a call to {@link #update(Command)} sets the value.
48  * </p>
49  *
50  * @author David Graeff - Initial contribution
51  */
52 @NonNullByDefault
53 public abstract class Value {
54     protected State state = UnDefType.UNDEF;
55     protected final List<Class<? extends Command>> commandTypes;
56     private final String itemType;
57
58     protected Value(String itemType, List<Class<? extends Command>> commandTypes) {
59         this.itemType = itemType;
60         this.commandTypes = commandTypes;
61     }
62
63     /**
64      * Return a list of supported command types. The order of the list is important.
65      * <p>
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.
68      * </p>
69      * <p>
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.
73      * </p>
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.
78      */
79     public final List<Class<? extends Command>> getSupportedCommandTypes() {
80         return commandTypes;
81     }
82
83     /**
84      * Returns the item-type (one of {@link CoreItemFactory}).
85      */
86     public final String getItemType() {
87         return itemType;
88     }
89
90     /**
91      * Returns the current value state.
92      */
93     public final State getChannelState() {
94         return state;
95     }
96
97     public String getMQTTpublishValue(@Nullable String pattern) {
98         if (pattern == null) {
99             return state.format("%s");
100         }
101         return state.format(pattern);
102     }
103
104     /**
105      * Returns true if this is a binary type.
106      */
107     public boolean isBinary() {
108         return false;
109     }
110
111     /**
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.
115      */
116     public final void resetState() {
117         state = UnDefType.UNDEF;
118     }
119
120     /**
121      * Updates the internal value state with the given command.
122      *
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.
125      */
126     public abstract void update(Command command) throws IllegalArgumentException;
127
128     /**
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.
132      *
133      * @param command The command to decide about
134      */
135     public @Nullable Command isPostOnly(Command command) {
136         return null;
137     }
138
139     /**
140      * Updates the internal value state with the given binary payload.
141      *
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.
144      */
145     public void update(byte data[]) throws IllegalArgumentException {
146         String mimeType = null;
147
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";
152         } else {
153             try (final ByteArrayInputStream input = new ByteArrayInputStream(data)) {
154                 try {
155                     mimeType = URLConnection.guessContentTypeFromStream(input);
156                 } catch (final IOException ignored) {
157                 }
158             } catch (final IOException ignored) {
159             }
160         }
161         state = new RawType(data, mimeType == null ? RawType.DEFAULT_MIME_TYPE : mimeType);
162     }
163
164     /**
165      * Return the state description fragment builder for this value state.
166      *
167      * @param readOnly True if this is a read-only value.
168      * @return A state description fragment builder
169      */
170     public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
171         return StateDescriptionFragmentBuilder.create().withReadOnly(readOnly).withPattern("%s");
172     }
173
174     /**
175      * Return the command description builder for this value state.
176      *
177      * @return A command description builder
178      */
179     public CommandDescriptionBuilder createCommandDescription() {
180         return CommandDescriptionBuilder.create();
181     }
182 }