]> git.basschouten.com Git - openhab-addons.git/blob
22c24311b6b74ae4fb33c43df91385df0e2e21a7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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     private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
69
70     private static final SimpleDateFormat WEEK_FORMAT = new SimpleDateFormat("EEE,HH:mm");
71
72     static {
73         synchronized (DATE_FORMAT) {
74             DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
75         }
76         synchronized (TIME_FORMAT) {
77             TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
78         }
79         synchronized (WEEK_FORMAT) {
80             WEEK_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
81         }
82     }
83
84     public ResolThingHandler(Thing thing, ResolStateDescriptionOptionProvider stateDescriptionProvider) {
85         super(thing);
86         this.stateDescriptionProvider = stateDescriptionProvider;
87     }
88
89     @Override
90     public void handleCommand(ChannelUID channelUID, Command command) {
91         /* we ignore the commands for now on purpose */
92     }
93
94     @Override
95     public void initialize() {
96         ResolBridgeHandler bridgeHandler = getBridgeHandler();
97         if (bridgeHandler != null) {
98             updateStatus(bridgeHandler.getStatus());
99         }
100     }
101
102     private synchronized @Nullable ResolBridgeHandler getBridgeHandler() {
103         Bridge bridge = getBridge();
104         if (bridge == null) {
105             logger.debug("Required bridge not defined for device.");
106             return null;
107         } else {
108             return getBridgeHandler(bridge);
109         }
110     }
111
112     private synchronized @Nullable ResolBridgeHandler getBridgeHandler(Bridge bridge) {
113         ResolBridgeHandler bridgeHandler = null;
114
115         ThingHandler handler = bridge.getHandler();
116         if (handler instanceof ResolBridgeHandler) {
117             bridgeHandler = (ResolBridgeHandler) handler;
118         } else {
119             logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
120         }
121         return bridgeHandler;
122     }
123
124     @Override
125     protected void packetReceived(Specification spec, Language lang, Packet packet) {
126         PacketFieldValue[] pfvs = spec.getPacketFieldValuesForHeaders(new Packet[] { packet });
127         for (PacketFieldValue pfv : pfvs) {
128             logger.trace("Id: {}, Name: {}, Raw: {}, Text: {}", pfv.getPacketFieldId(), pfv.getName(lang),
129                     pfv.getRawValueDouble(), pfv.formatTextValue(null, Locale.getDefault()));
130
131             String channelId = pfv.getName(); // use English name as channel
132             channelId = channelId.replace(" [", "-");
133             channelId = channelId.replace("]", "");
134             channelId = channelId.replace("(", "-");
135             channelId = channelId.replace(")", "");
136             channelId = channelId.replace(" #", "-");
137             channelId = channelId.replaceAll("[^A-Za-z0-9_-]+", "_");
138
139             channelId = channelId.toLowerCase(Locale.ENGLISH);
140
141             ChannelTypeUID channelTypeUID;
142
143             if (pfv.getPacketFieldSpec().getUnit().getUnitId() >= 0) {
144                 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID,
145                         pfv.getPacketFieldSpec().getUnit().getUnitCodeText());
146             } else if (pfv.getPacketFieldSpec().getType() == SpecificationFile.Type.DateTime) {
147                 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "datetime");
148             } else if (pfv.getPacketFieldSpec().getType() == SpecificationFile.Type.WeekTime) {
149                 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "weektime");
150             } else if (pfv.getPacketFieldSpec().getType() == SpecificationFile.Type.Time) {
151                 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "time");
152             } else {
153                 /* used for enums and the numeric types without unit */
154                 channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "None");
155             }
156
157             String acceptedItemType;
158
159             Thing thing = getThing();
160             switch (pfv.getPacketFieldSpec().getType()) {
161                 case DateTime:
162                     acceptedItemType = "DateTime";
163                     break;
164                 case Number:
165                     acceptedItemType = ResolChannelTypeProvider.itemTypeForUnit(pfv.getPacketFieldSpec().getUnit());
166                     break;
167                 case WeekTime:
168                 case Time:
169                 default:
170                     acceptedItemType = "String";
171                     break;
172             }
173             Channel a = thing.getChannel(channelId);
174
175             if (a == null) {
176                 /* channel doesn't exit, let's create it */
177                 ThingBuilder thingBuilder = editThing();
178                 ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
179
180                 if (pfv.getEnumVariant() != null) {
181                     /* create a state option channel */
182                     List<StateOption> options = new ArrayList<>();
183                     PacketFieldSpec ff = pfv.getPacketFieldSpec();
184                     Enum e = ff.getEnum();
185                     for (long l : e.getValues()) {
186                         EnumVariant v = e.getEnumVariantForValue(l);
187                         options.add(new StateOption(Long.toString(l), v.getText(lang)));
188                     }
189
190                     stateDescriptionProvider.setStateOptions(channelUID, options);
191
192                     Channel channel = ChannelBuilder.create(channelUID, "Number").withType(channelTypeUID)
193                             .withLabel(pfv.getName(lang)).build();
194
195                     thingBuilder.withChannel(channel).withLabel(thing.getLabel());
196                     updateThing(thingBuilder.build());
197                 } else if ("DateTime".equals(acceptedItemType)) {
198                     /* a date channel */
199                     Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
200                             .withLabel(pfv.getName(lang)).build();
201
202                     thingBuilder.withChannel(channel).withLabel(thing.getLabel());
203                     updateThing(thingBuilder.build());
204
205                 } else if ("String".equals(acceptedItemType)) {
206                     /* a string channel */
207                     Channel channel = ChannelBuilder.create(channelUID, "String").withType(channelTypeUID)
208                             .withLabel(pfv.getName(lang)).build();
209
210                     thingBuilder.withChannel(channel).withLabel(thing.getLabel());
211                     updateThing(thingBuilder.build());
212                 } else if (pfv.getRawValueDouble() != null) {
213                     /* a number channel */
214                     Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
215                             .withLabel(pfv.getName(lang)).build();
216
217                     thingBuilder.withChannel(channel).withLabel(thing.getLabel());
218                     updateThing(thingBuilder.build());
219                 } else {
220                     /* a string channel */
221                     Channel channel = ChannelBuilder.create(channelUID, "String").withType(channelTypeUID)
222                             .withLabel(pfv.getName(lang)).build();
223
224                     thingBuilder.withChannel(channel).withLabel(thing.getLabel());
225                     updateThing(thingBuilder.build());
226                 }
227                 logger.debug("Creating channel: {}", channelUID);
228             }
229
230             if (pfv.getEnumVariant() != null) {
231                 /* update the enum / State channel */
232                 this.updateState(channelId, new StringType(Long.toString(pfv.getRawValueLong())));
233
234             } else {
235                 switch (pfv.getPacketFieldSpec().getType()) {
236                     case Number:
237                         Double dd = pfv.getRawValueDouble();
238                         if (dd != null) {
239                             if (!isSpecialValue(dd)) {
240                                 /* only set the value if no error occurred */
241
242                                 String str = pfv.formatText();
243                                 if (str.endsWith("RH")) {
244                                     /* unit %RH for relative humidity is not known in openHAB UoM, so we remove it */
245                                     str = str.substring(0, str.length() - 2);
246                                 }
247                                 if (str.endsWith("Ω")) {
248                                     QuantityType<?> q = new QuantityType<>(dd, Units.OHM);
249                                     this.updateState(channelId, q);
250                                 } else {
251                                     try {
252                                         QuantityType<?> q = new QuantityType<>(str, Locale
253                                                 .getDefault()); /* vbus library returns the value in default locale */
254                                         this.updateState(channelId, q);
255                                     } catch (IllegalArgumentException e) {
256                                         logger.debug("unit of '{}' unknown in openHAB", str);
257                                         QuantityType<?> q = new QuantityType<>(dd, Units.ONE);
258                                         this.updateState(channelId, q);
259                                     }
260                                 }
261                             }
262                         }
263                         /*
264                          * else {
265                          * field not available in this packet, e. g. old firmware version not (yet) transmitting it
266                          * }
267                          */
268                         break;
269                     case Time:
270                         synchronized (TIME_FORMAT) {
271                             this.updateState(channelId, new StringType(TIME_FORMAT.format(pfv.getRawValueDate())));
272                         }
273                         break;
274                     case WeekTime:
275                         synchronized (WEEK_FORMAT) {
276                             this.updateState(channelId, new StringType(WEEK_FORMAT.format(pfv.getRawValueDate())));
277                         }
278                         break;
279                     case DateTime:
280                         synchronized (DATE_FORMAT) {
281                             DateTimeType d = new DateTimeType(DATE_FORMAT.format(pfv.getRawValueDate()));
282                             this.updateState(channelId, d);
283                         }
284                         break;
285                     default:
286                         Bridge b = getBridge();
287                         if (b != null) {
288                             ResolBridgeHandler handler = (ResolBridgeHandler) b.getHandler();
289                             String value;
290                             Locale loc;
291                             if (handler != null) {
292                                 loc = handler.getLocale();
293                             } else {
294                                 loc = Locale.getDefault();
295                             }
296                             value = pfv.formatTextValue(pfv.getPacketFieldSpec().getUnit(), loc);
297                             try {
298                                 QuantityType<?> q = new QuantityType<>(value, loc);
299                                 this.updateState(channelId, q);
300                             } catch (IllegalArgumentException e) {
301                                 this.updateState(channelId, new StringType(value));
302                                 logger.debug("unit of '{}' unknown in openHAB, using string", value);
303                             }
304                         }
305                 }
306             }
307         }
308     }
309
310     /* check if the given value is a special one like 888.8 or 999.9 for shortcut or open load on a sensor wire */
311     private boolean isSpecialValue(Double dd) {
312         if ((Math.abs(dd - 888.8) < 0.1) || (Math.abs(dd - (-888.8)) < 0.1)) {
313             /* value out of range */
314             return true;
315         }
316         if (Math.abs(dd - 999.9) < 0.1) {
317             /* sensor not reachable */
318             return true;
319         }
320         return false;
321     }
322 }