2 * Copyright (c) 2010-2021 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.resol.handler;
15 import java.text.SimpleDateFormat;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.TimeZone;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.resol.internal.ResolBindingConstants;
24 import org.openhab.binding.resol.internal.ResolStateDescriptionOptionProvider;
25 import org.openhab.binding.resol.internal.providers.ResolChannelTypeProvider;
26 import org.openhab.core.library.types.DateTimeType;
27 import org.openhab.core.library.types.QuantityType;
28 import org.openhab.core.library.types.StringType;
29 import org.openhab.core.library.unit.Units;
30 import org.openhab.core.thing.Bridge;
31 import org.openhab.core.thing.Channel;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.binding.ThingHandler;
35 import org.openhab.core.thing.binding.builder.ChannelBuilder;
36 import org.openhab.core.thing.binding.builder.ThingBuilder;
37 import org.openhab.core.thing.type.ChannelTypeUID;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.StateOption;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import de.resol.vbus.Packet;
44 import de.resol.vbus.Specification;
45 import de.resol.vbus.Specification.PacketFieldSpec;
46 import de.resol.vbus.Specification.PacketFieldValue;
47 import de.resol.vbus.SpecificationFile;
48 import de.resol.vbus.SpecificationFile.Enum;
49 import de.resol.vbus.SpecificationFile.EnumVariant;
50 import de.resol.vbus.SpecificationFile.Language;
53 * The {@link ResolThingHandler} is responsible for handling commands, which are
54 * sent to one of the channels.
56 * @author Raphael Mack - Initial contribution
59 public class ResolThingHandler extends ResolBaseThingHandler {
61 private final Logger logger = LoggerFactory.getLogger(ResolThingHandler.class);
63 private ResolStateDescriptionOptionProvider stateDescriptionProvider;
65 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
66 DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS_GENERAL);
69 synchronized (DATE_FORMAT) {
70 DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
74 public ResolThingHandler(Thing thing, ResolStateDescriptionOptionProvider stateDescriptionProvider) {
76 this.stateDescriptionProvider = stateDescriptionProvider;
80 public void handleCommand(ChannelUID channelUID, Command command) {
81 /* we ignore the commands for now on purpose */
85 public void initialize() {
86 ResolBridgeHandler bridgeHandler = getBridgeHandler();
87 if (bridgeHandler != null) {
88 updateStatus(bridgeHandler.getStatus());
92 private synchronized @Nullable ResolBridgeHandler getBridgeHandler() {
93 Bridge bridge = getBridge();
95 logger.debug("Required bridge not defined for device.");
98 return getBridgeHandler(bridge);
102 private synchronized @Nullable ResolBridgeHandler getBridgeHandler(Bridge bridge) {
103 ResolBridgeHandler bridgeHandler = null;
105 ThingHandler handler = bridge.getHandler();
106 if (handler instanceof ResolBridgeHandler) {
107 bridgeHandler = (ResolBridgeHandler) handler;
109 logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
111 return bridgeHandler;
115 protected void packetReceived(Specification spec, Language lang, Packet packet) {
116 PacketFieldValue[] pfvs = spec.getPacketFieldValuesForHeaders(new Packet[] { packet });
117 for (PacketFieldValue pfv : pfvs) {
118 logger.trace("Id: {}, Name: {}, Raw: {}, Text: {}", pfv.getPacketFieldId(), pfv.getName(lang),
119 pfv.getRawValueDouble(), pfv.formatTextValue(null, Locale.getDefault()));
121 String channelId = pfv.getName(); // use English name as channel
122 channelId = channelId.replace(" [", "-");
123 channelId = channelId.replace("]", "");
124 channelId = channelId.replace("(", "-");
125 channelId = channelId.replace(")", "");
126 channelId = channelId.replace(" #", "-");
127 channelId = channelId.replaceAll("[^A-Za-z0-9_-]+", "_");
129 channelId = channelId.toLowerCase(Locale.ENGLISH);
131 ChannelTypeUID channelTypeUID;
133 if (pfv.getPacketFieldSpec().getUnit().getUnitId() >= 0) {
134 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID,
135 pfv.getPacketFieldSpec().getUnit().getUnitCodeText());
136 } else if (pfv.getPacketFieldSpec().getType() == SpecificationFile.Type.DateTime) {
137 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "DateTime");
139 /* used for enums and the numeric types without unit */
140 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "None");
143 String acceptedItemType;
145 Thing thing = getThing();
146 switch (pfv.getPacketFieldSpec().getType()) {
148 acceptedItemType = "DateTime";
152 acceptedItemType = ResolChannelTypeProvider.itemTypeForUnit(pfv.getPacketFieldSpec().getUnit());
156 acceptedItemType = "String";
159 Channel a = thing.getChannel(channelId);
162 /* channel doesn't exit, let's create it */
163 ThingBuilder thingBuilder = editThing();
164 ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
166 if (pfv.getEnumVariant() != null) {
167 /* create a state option channel */
168 List<StateOption> options = new ArrayList<>();
169 PacketFieldSpec ff = pfv.getPacketFieldSpec();
170 Enum e = ff.getEnum();
171 for (long l : e.getValues()) {
172 EnumVariant v = e.getEnumVariantForValue(l);
173 options.add(new StateOption(Long.toString(l), v.getText(lang)));
176 stateDescriptionProvider.setStateOptions(channelUID, options);
178 Channel channel = ChannelBuilder.create(channelUID, "Number").withType(channelTypeUID)
179 .withLabel(pfv.getName(lang)).build();
181 thingBuilder.withChannel(channel).withLabel(thing.getLabel());
182 updateThing(thingBuilder.build());
183 } else if (pfv.getRawValueDouble() != null) {
184 /* a number channel */
185 Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
186 .withLabel(pfv.getName(lang)).build();
188 thingBuilder.withChannel(channel).withLabel(thing.getLabel());
189 updateThing(thingBuilder.build());
191 logger.debug("Creating channel: {}", channelUID);
194 if (pfv.getEnumVariant() != null) {
195 /* update the enum / State channel */
196 this.updateState(channelId, new StringType(Long.toString(pfv.getRawValueLong())));
199 switch (pfv.getPacketFieldSpec().getType()) {
201 Double dd = pfv.getRawValueDouble();
203 if (!isSpecialValue(dd)) {
204 /* only set the value if no error occurred */
206 String str = pfv.formatText();
207 if (str.endsWith("RH")) {
208 /* unit %RH for relative humidity is not known in openHAB UoM, so we remove it */
209 str = str.substring(0, str.length() - 2);
211 if (str.endsWith("Ω")) {
212 QuantityType<?> q = new QuantityType<>(dd, Units.OHM);
213 this.updateState(channelId, q);
216 QuantityType<?> q = new QuantityType<>(str);
217 this.updateState(channelId, q);
218 } catch (IllegalArgumentException e) {
219 logger.debug("unit of '{}' unknown in openHAB", str);
220 QuantityType<?> q = new QuantityType<>(dd.toString());
221 this.updateState(channelId, q);
228 * field not available in this packet, e. g. old firmware version not (yet) transmitting it
233 synchronized (DATE_FORMAT) {
234 DateTimeType d = new DateTimeType(DATE_FORMAT.format(pfv.getRawValueDate()));
235 this.updateState(channelId, d);
241 Bridge b = getBridge();
243 String value = pfv.formatTextValue(pfv.getPacketFieldSpec().getUnit(),
244 ((ResolBridgeHandler) b).getLocale());
246 QuantityType<?> q = new QuantityType<>(value);
247 this.updateState(channelId, q);
248 } catch (IllegalArgumentException e) {
249 this.updateState(channelId, new StringType(value));
250 logger.debug("unit of '{}' unknown in openHAB, using string", value);
258 /* check if the given value is a special one like 888.8 or 999.9 for shortcut or open load on a sensor wire */
259 private boolean isSpecialValue(Double dd) {
260 if ((Math.abs(dd - 888.8) < 0.1) || (Math.abs(dd - (-888.8)) < 0.1)) {
261 /* value out of range */
264 if (Math.abs(dd - 999.9) < 0.1) {
265 /* sensor not reachable */