]> git.basschouten.com Git - openhab-addons.git/blob
76839cf514704b46f739e8d0455e9a14e4b55f1e
[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.Type;
31 import org.openhab.core.types.UnDefType;
32
33 /**
34  * MQTT topics are not inherently typed.
35  *
36  * <p>
37  * With this class users are able to map MQTT topic values to framework types,
38  * for example for numbers {@link NumberValue}, boolean values {@link OnOffValue}, percentage values
39  * {@link PercentageValue}, string values {@link TextValue} and more.
40  * </p>
41  *
42  * <p>
43  * This class and the encapsulated (cached) state are necessary, because MQTT can't be queried,
44  * but we still need to be able to respond to framework requests for a value.
45  * </p>
46  *
47  * <p>
48  * {@link #getCache()} is used to retrieve a topic state and a call to {@link #update(Command)} sets the value.
49  * </p>
50  *
51  * @author David Graeff - Initial contribution
52  */
53 @NonNullByDefault
54 public abstract class Value {
55     protected State state = UnDefType.UNDEF;
56     protected final List<Class<? extends Command>> commandTypes;
57     private final String itemType;
58
59     protected Value(String itemType, List<Class<? extends Command>> commandTypes) {
60         this.itemType = itemType;
61         this.commandTypes = commandTypes;
62     }
63
64     /**
65      * Return a list of supported command types. The order of the list is important.
66      * <p>
67      * The framework will try to parse an incoming string into one of those command types,
68      * starting with the first and continue until it succeeds.
69      * </p>
70      * <p>
71      * Your {@link #update(Command)} method must accept all command types of this list.
72      * You may accept more command types. This allows you to restrict the parsing of incoming
73      * MQTT values to the listed types, but handle more user commands.
74      * </p>
75      * A prominent example is the {@link NumberValue}, which does not return {@link PercentType},
76      * because that would interfere with {@link DecimalType} while parsing the MQTT value.
77      * It does however accept a {@link PercentType} for {@link #update(Command)}, because a
78      * linked Item could send that type of command.
79      */
80     public final List<Class<? extends Command>> getSupportedCommandTypes() {
81         return commandTypes;
82     }
83
84     /**
85      * Returns the item-type (one of {@link CoreItemFactory}).
86      */
87     public final String getItemType() {
88         return itemType;
89     }
90
91     /**
92      * Returns the current value state.
93      */
94     public final State getChannelState() {
95         return state;
96     }
97
98     public String getMQTTpublishValue(Command command, @Nullable String pattern) {
99         if (pattern == null) {
100             return command.format("%s");
101         }
102         return command.format(pattern);
103     }
104
105     /**
106      * Returns true if this is a binary type.
107      */
108     public boolean isBinary() {
109         return false;
110     }
111
112     /**
113      * If the MQTT connection is not yet initialised or no values have
114      * been received yet, the default value is {@link UnDefType#UNDEF}. To restore to the
115      * default value after a connection got lost etc, this method will be called.
116      */
117     public final void resetState() {
118         state = UnDefType.UNDEF;
119     }
120
121     /**
122      * Updates the internal value state with the given state.
123      *
124      * @param newState The new state to update the internal value.
125      * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
126      */
127     public void update(State newState) throws IllegalArgumentException {
128         state = newState;
129     }
130
131     /**
132      * Parses a given command into the proper type for this Value type. This will usually be a State,
133      * but can be a Command.
134      *
135      * @param command The command to parse.
136      * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
137      */
138     public abstract Command parseCommand(Command command) throws IllegalArgumentException;
139
140     /**
141      * Parses a given command from MQTT into the proper type for this Value type. This will usually
142      * be a State, but can be a non-State Command, in which case the channel will be commanded instead
143      * of updated, regardless of postCommand setting. The default implementation just calls
144      * parseCommand, so that both directions have the same logic.
145      *
146      * @param command The command to parse.
147      * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
148      */
149     public Type parseMessage(Command command) throws IllegalArgumentException {
150         return parseCommand(command);
151     }
152
153     /**
154      * Updates the internal value state with the given binary payload.
155      *
156      * @param data The binary payload to update the internal value.
157      * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type.
158      */
159     public void update(byte[] data) throws IllegalArgumentException {
160         String mimeType = null;
161
162         // URLConnection.guessContentTypeFromStream(input) is not sufficient to detect all JPEG files
163         if (data.length >= 2 && data[0] == (byte) 0xFF && data[1] == (byte) 0xD8 && data[data.length - 2] == (byte) 0xFF
164                 && data[data.length - 1] == (byte) 0xD9) {
165             mimeType = "image/jpeg";
166         } else {
167             try (final ByteArrayInputStream input = new ByteArrayInputStream(data)) {
168                 try {
169                     mimeType = URLConnection.guessContentTypeFromStream(input);
170                 } catch (final IOException ignored) {
171                 }
172             } catch (final IOException ignored) {
173             }
174         }
175         state = new RawType(data, mimeType == null ? RawType.DEFAULT_MIME_TYPE : mimeType);
176     }
177
178     /**
179      * Return the state description fragment builder for this value state.
180      *
181      * @param readOnly True if this is a read-only value.
182      * @return A state description fragment builder
183      */
184     public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
185         return StateDescriptionFragmentBuilder.create().withReadOnly(readOnly).withPattern("%s");
186     }
187
188     /**
189      * Return the command description builder for this value state.
190      *
191      * @return A command description builder
192      */
193     public CommandDescriptionBuilder createCommandDescription() {
194         return CommandDescriptionBuilder.create();
195     }
196 }