]> git.basschouten.com Git - openhab-addons.git/blob
f57afd763492a8e47b4369799c34b0b33cbab767
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.resol.handler;
14
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;
20
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;
42
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;
51
52 /**
53  * The {@link ResolThingHandler} is responsible for handling commands, which are
54  * sent to one of the channels.
55  *
56  * @author Raphael Mack - Initial contribution
57  */
58 @NonNullByDefault
59 public class ResolThingHandler extends ResolBaseThingHandler {
60
61     private final Logger logger = LoggerFactory.getLogger(ResolThingHandler.class);
62
63     private ResolStateDescriptionOptionProvider stateDescriptionProvider;
64
65     private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
66             DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS_GENERAL);
67
68     static {
69         synchronized (DATE_FORMAT) {
70             DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
71         }
72     }
73
74     public ResolThingHandler(Thing thing, ResolStateDescriptionOptionProvider stateDescriptionProvider) {
75         super(thing);
76         this.stateDescriptionProvider = stateDescriptionProvider;
77     }
78
79     @Override
80     public void handleCommand(ChannelUID channelUID, Command command) {
81         /* we ignore the commands for now on purpose */
82     }
83
84     @Override
85     public void initialize() {
86         ResolBridgeHandler bridgeHandler = getBridgeHandler();
87         if (bridgeHandler != null) {
88             updateStatus(bridgeHandler.getStatus());
89         }
90     }
91
92     private synchronized @Nullable ResolBridgeHandler getBridgeHandler() {
93         Bridge bridge = getBridge();
94         if (bridge == null) {
95             logger.debug("Required bridge not defined for device.");
96             return null;
97         } else {
98             return getBridgeHandler(bridge);
99         }
100     }
101
102     private synchronized @Nullable ResolBridgeHandler getBridgeHandler(Bridge bridge) {
103         ResolBridgeHandler bridgeHandler = null;
104
105         ThingHandler handler = bridge.getHandler();
106         if (handler instanceof ResolBridgeHandler) {
107             bridgeHandler = (ResolBridgeHandler) handler;
108         } else {
109             logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
110         }
111         return bridgeHandler;
112     }
113
114     @Override
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()));
120
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_-]+", "_");
128
129             channelId = channelId.toLowerCase(Locale.ENGLISH);
130
131             ChannelTypeUID channelTypeUID;
132
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");
138             } else {
139                 /* used for enums and the numeric types without unit */
140                 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "None");
141             }
142
143             String acceptedItemType;
144
145             Thing thing = getThing();
146             switch (pfv.getPacketFieldSpec().getType()) {
147                 case DateTime:
148                     acceptedItemType = "DateTime";
149                     break;
150                 case WeekTime:
151                 case Number:
152                     acceptedItemType = ResolChannelTypeProvider.itemTypeForUnit(pfv.getPacketFieldSpec().getUnit());
153                     break;
154                 case Time:
155                 default:
156                     acceptedItemType = "String";
157                     break;
158             }
159             Channel a = thing.getChannel(channelId);
160
161             if (a == null) {
162                 /* channel doesn't exit, let's create it */
163                 ThingBuilder thingBuilder = editThing();
164                 ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
165
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)));
174                     }
175
176                     stateDescriptionProvider.setStateOptions(channelUID, options);
177
178                     Channel channel = ChannelBuilder.create(channelUID, "Number").withType(channelTypeUID)
179                             .withLabel(pfv.getName(lang)).build();
180
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();
187
188                     thingBuilder.withChannel(channel).withLabel(thing.getLabel());
189                     updateThing(thingBuilder.build());
190                 }
191                 logger.debug("Creating channel: {}", channelUID);
192             }
193
194             if (pfv.getEnumVariant() != null) {
195                 /* update the enum / State channel */
196                 this.updateState(channelId, new StringType(Long.toString(pfv.getRawValueLong())));
197
198             } else {
199                 switch (pfv.getPacketFieldSpec().getType()) {
200                     case Number:
201                         Double dd = pfv.getRawValueDouble();
202                         if (dd != null) {
203                             if (!isSpecialValue(dd)) {
204                                 /* only set the value if no error occurred */
205
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);
210                                 }
211                                 if (str.endsWith("Ω")) {
212                                     QuantityType<?> q = new QuantityType<>(dd, Units.OHM);
213                                     this.updateState(channelId, q);
214                                 } else {
215                                     try {
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);
222                                     }
223                                 }
224                             }
225                         }
226                         /*
227                          * else {
228                          * field not available in this packet, e. g. old firmware version not (yet) transmitting it
229                          * }
230                          */
231                         break;
232                     case DateTime:
233                         synchronized (DATE_FORMAT) {
234                             DateTimeType d = new DateTimeType(DATE_FORMAT.format(pfv.getRawValueDate()));
235                             this.updateState(channelId, d);
236                         }
237                         break;
238                     case WeekTime:
239                     case Time:
240                     default:
241                         Bridge b = getBridge();
242                         if (b != null) {
243                             String value = pfv.formatTextValue(pfv.getPacketFieldSpec().getUnit(),
244                                     ((ResolBridgeHandler) b).getLocale());
245                             try {
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);
251                             }
252                         }
253                 }
254             }
255         }
256     }
257
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 */
262             return true;
263         }
264         if (Math.abs(dd - 999.9) < 0.1) {
265             /* sensor not reachable */
266             return true;
267         }
268         return false;
269     }
270 }