]> git.basschouten.com Git - openhab-addons.git/blob
c1a13d80b98e27b9967427897608c7fe7eab5737
[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.onewire.internal.handler;
14
15 import static org.openhab.binding.onewire.internal.OwBindingConstants.*;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.onewire.internal.OwDynamicStateDescriptionProvider;
30 import org.openhab.binding.onewire.internal.OwException;
31 import org.openhab.binding.onewire.internal.SensorId;
32 import org.openhab.binding.onewire.internal.config.BaseHandlerConfiguration;
33 import org.openhab.binding.onewire.internal.device.AbstractOwDevice;
34 import org.openhab.binding.onewire.internal.device.OwChannelConfig;
35 import org.openhab.binding.onewire.internal.device.OwSensorType;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.Channel;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.ThingStatusInfo;
45 import org.openhab.core.thing.binding.BaseThingHandler;
46 import org.openhab.core.thing.binding.builder.ChannelBuilder;
47 import org.openhab.core.thing.binding.builder.ThingBuilder;
48 import org.openhab.core.types.Command;
49 import org.openhab.core.types.RefreshType;
50 import org.openhab.core.types.State;
51 import org.openhab.core.types.UnDefType;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * The {@link OwBaseThingHandler} class defines a handler for simple OneWire devices
57  *
58  * @author Jan N. Klug - Initial contribution
59  */
60 @NonNullByDefault
61 public abstract class OwBaseThingHandler extends BaseThingHandler {
62     private final Logger logger = LoggerFactory.getLogger(OwBaseThingHandler.class);
63
64     protected static final int PROPERTY_UPDATE_INTERVAL = 5000; // in ms
65     protected static final int PROPERTY_UPDATE_MAX_RETRY = 5;
66
67     private static final Set<String> REQUIRED_PROPERTIES = Collections
68             .unmodifiableSet(Stream.of(PROPERTY_MODELID, PROPERTY_VENDOR).collect(Collectors.toSet()));
69
70     protected List<String> requiredProperties = new ArrayList<>(REQUIRED_PROPERTIES);
71     protected Set<OwSensorType> supportedSensorTypes;
72
73     protected final List<AbstractOwDevice> sensors = new ArrayList<>();
74     protected @NonNullByDefault({}) SensorId sensorId;
75     protected @NonNullByDefault({}) OwSensorType sensorType;
76
77     protected long lastRefresh = 0;
78     protected long refreshInterval = 300 * 1000;
79
80     protected boolean validConfig = false;
81     protected boolean showPresence = false;
82
83     protected OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider;
84
85     protected @Nullable ScheduledFuture<?> updateTask;
86
87     public OwBaseThingHandler(Thing thing, OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider,
88             Set<OwSensorType> supportedSensorTypes) {
89         super(thing);
90
91         this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider;
92         this.supportedSensorTypes = supportedSensorTypes;
93     }
94
95     public OwBaseThingHandler(Thing thing, OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider,
96             Set<OwSensorType> supportedSensorTypes, Set<String> requiredProperties) {
97         super(thing);
98
99         this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider;
100         this.supportedSensorTypes = supportedSensorTypes;
101         this.requiredProperties.addAll(requiredProperties);
102     }
103
104     @Override
105     public void handleCommand(ChannelUID channelUID, Command command) {
106         if (command instanceof RefreshType) {
107             lastRefresh = 0;
108             logger.trace("scheduled {} for refresh", this.thing.getUID());
109         }
110     }
111
112     @Override
113     public void initialize() {
114         configureThingHandler();
115     }
116
117     protected boolean configureThingHandler() {
118         BaseHandlerConfiguration configuration = getConfig().as(BaseHandlerConfiguration.class);
119         Map<String, String> properties = thing.getProperties();
120
121         if (getBridge() == null) {
122             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge missing");
123             return false;
124         }
125         sensors.clear();
126
127         final String id = configuration.id;
128         if (id != null) {
129             try {
130                 this.sensorId = new SensorId(id);
131             } catch (IllegalArgumentException e) {
132                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "sensor id format mismatch");
133                 return false;
134             }
135         } else {
136             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "sensor id missing");
137             return false;
138         }
139
140         refreshInterval = configuration.refresh * 1000;
141
142         // check if all required properties are present. update if not
143         for (String property : requiredProperties) {
144             if (!properties.containsKey(property)) {
145                 updateSensorProperties();
146                 return false;
147             }
148         }
149
150         sensorType = OwSensorType.valueOf(properties.get(PROPERTY_MODELID));
151         if (!supportedSensorTypes.contains(sensorType)) {
152             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
153                     "sensor type not supported by this thing type");
154             return false;
155         }
156
157         lastRefresh = 0;
158         return true;
159     }
160
161     protected void configureThingChannels() {
162         ThingBuilder thingBuilder = editThing();
163
164         logger.debug("configuring sensors for {}", thing.getUID());
165
166         // remove unwanted channels
167         Set<String> existingChannelIds = thing.getChannels().stream().map(channel -> channel.getUID().getId())
168                 .collect(Collectors.toSet());
169         Set<String> wantedChannelIds = SENSOR_TYPE_CHANNEL_MAP.get(sensorType).stream()
170                 .map(channelConfig -> channelConfig.channelId).collect(Collectors.toSet());
171         existingChannelIds.stream().filter(channelId -> !wantedChannelIds.contains(channelId))
172                 .forEach(channelId -> removeChannelIfExisting(thingBuilder, channelId));
173
174         // add or update wanted channels
175         SENSOR_TYPE_CHANNEL_MAP.get(sensorType).stream().forEach(channelConfig -> {
176             addChannelIfMissingAndEnable(thingBuilder, channelConfig);
177         });
178
179         updateThing(thingBuilder.build());
180
181         try {
182             sensors.get(0).configureChannels();
183         } catch (OwException e) {
184             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
185             return;
186         }
187
188         if (thing.getChannel(CHANNEL_PRESENT) != null) {
189             showPresence = true;
190         }
191
192         validConfig = true;
193         updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
194     }
195
196     /**
197      * check if thing can be refreshed from the bridge handler
198      *
199      * @return true if thing can be refreshed
200      */
201     public boolean isRefreshable() {
202         return super.isInitialized()
203                 && this.thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR
204                 && this.thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.BRIDGE_OFFLINE;
205     }
206
207     /**
208      * refresh this thing
209      *
210      * needs proper exception handling for refresh errors if overridden
211      *
212      * @param bridgeHandler bridge handler to use for communication with ow bus
213      * @param now current time
214      */
215     public void refresh(OwserverBridgeHandler bridgeHandler, long now) {
216         try {
217             Boolean forcedRefresh = lastRefresh == 0;
218             if (now >= (lastRefresh + refreshInterval)) {
219                 logger.trace("refreshing {}", this.thing.getUID());
220
221                 lastRefresh = now;
222
223                 if (!sensors.get(0).checkPresence(bridgeHandler)) {
224                     logger.trace("sensor not present");
225                     return;
226                 }
227
228                 for (int i = 0; i < sensors.size(); i++) {
229                     logger.trace("refreshing sensor {} ({})", i, sensors.get(i).getSensorId());
230                     sensors.get(i).refresh(bridgeHandler, forcedRefresh);
231                 }
232             }
233         } catch (OwException e) {
234             logger.debug("{}: refresh exception {}", this.thing.getUID(), e.getMessage());
235             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "refresh exception");
236         }
237     }
238
239     /**
240      * update presence status to present state of slave
241      *
242      * @param presentState current present state
243      */
244     public void updatePresenceStatus(State presentState) {
245         if (OnOffType.ON.equals(presentState)) {
246             updateStatus(ThingStatus.ONLINE);
247             if (showPresence) {
248                 updateState(CHANNEL_PRESENT, OnOffType.ON);
249             }
250         } else if (OnOffType.OFF.equals(presentState)) {
251             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "slave missing");
252             if (showPresence) {
253                 updateState(CHANNEL_PRESENT, OnOffType.OFF);
254             }
255         } else {
256             updateStatus(ThingStatus.UNKNOWN);
257             if (showPresence) {
258                 updateState(CHANNEL_PRESENT, UnDefType.UNDEF);
259             }
260         }
261     }
262
263     /**
264      * post update to channel
265      *
266      * @param channelId channel id
267      * @param state new channel state
268      */
269     public void postUpdate(String channelId, State state) {
270         if (this.thing.getChannel(channelId) != null) {
271             updateState(channelId, state);
272         } else {
273             logger.warn("{} missing channel {} when posting update {}", this.thing.getUID(), channelId, state);
274         }
275     }
276
277     @Override
278     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
279         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
280                 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
281             if (validConfig) {
282                 updatePresenceStatus(UnDefType.UNDEF);
283             } else {
284                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
285             }
286         } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
287             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
288         }
289     }
290
291     @Override
292     public void dispose() {
293         dynamicStateDescriptionProvider.removeDescriptionsForThing(thing.getUID());
294         super.dispose();
295     }
296
297     /**
298      * add this sensor to the property update list of the bridge handler
299      *
300      */
301     protected void updateSensorProperties() {
302         Bridge bridge = getBridge();
303         if (bridge == null) {
304             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge not found");
305             return;
306         }
307
308         OwserverBridgeHandler bridgeHandler = (OwserverBridgeHandler) bridge.getHandler();
309         if (bridgeHandler == null) {
310             logger.debug("bridgehandler for {} not available for scheduling property update, retrying in 5s",
311                     thing.getUID());
312             scheduler.schedule(() -> {
313                 updateSensorProperties();
314             }, 5000, TimeUnit.MILLISECONDS);
315             return;
316         }
317
318         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "required properties missing");
319         bridgeHandler.scheduleForPropertiesUpdate(thing);
320     }
321
322     /**
323      * thing specific update method for sensor properties
324      *
325      * called by the bridge handler
326      *
327      * @param bridgeHandler the bridge handler to be used
328      * @return properties to be added to the properties map
329      * @throws OwException
330      */
331     public void updateSensorProperties(OwserverBridgeHandler bridgeHandler) throws OwException {
332         Map<String, String> properties = editProperties();
333         OwSensorType sensorType = bridgeHandler.getType(sensorId);
334         properties.put(PROPERTY_MODELID, sensorType.toString());
335         properties.put(PROPERTY_VENDOR, "Dallas/Maxim");
336
337         updateProperties(properties);
338
339         logger.trace("updated modelid/vendor to {} / {}", sensorType.name(), "Dallas/Maxim");
340     }
341
342     /**
343      * get the dynamic state description provider for this thing
344      *
345      * @return
346      */
347     public @Nullable OwDynamicStateDescriptionProvider getDynamicStateDescriptionProvider() {
348         return dynamicStateDescriptionProvider;
349     }
350
351     /**
352      * remove a channel during initialization if it exists
353      *
354      * @param thingBuilder ThingBuilder of the edited thing
355      * @param channelId id of the channel
356      */
357     protected void removeChannelIfExisting(ThingBuilder thingBuilder, String channelId) {
358         if (thing.getChannel(channelId) != null) {
359             thingBuilder.withoutChannel(new ChannelUID(thing.getUID(), channelId));
360         }
361     }
362
363     /**
364      * adds (or replaces) a channel and enables it within the sensor (configuration preserved, default sensor)
365      *
366      * @param thingBuilder ThingBuilder of the edited thing
367      * @param channelConfig a OwChannelConfig for the new channel
368      * @return the newly created channel
369      */
370     protected Channel addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig) {
371         return addChannelIfMissingAndEnable(thingBuilder, channelConfig, null, 0);
372     }
373
374     /**
375      * adds (or replaces) a channel and enables it within the sensor (configuration overridden, default sensor)
376      *
377      * @param thingBuilder ThingBuilder of the edited thing
378      * @param channelConfig a OwChannelConfig for the new channel
379      * @param configuration the new Configuration for this channel
380      * @return the newly created channel
381      */
382     protected Channel addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig,
383             Configuration configuration) {
384         return addChannelIfMissingAndEnable(thingBuilder, channelConfig, configuration, 0);
385     }
386
387     /**
388      * adds (or replaces) a channel and enables it within the sensor (configuration preserved)
389      *
390      * @param thingBuilder ThingBuilder of the edited thing
391      * @param channelConfig a OwChannelConfig for the new channel
392      * @param sensorNo number of sensor that provides this channel
393      * @return the newly created channel
394      */
395     protected Channel addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig,
396             int sensorNo) {
397         return addChannelIfMissingAndEnable(thingBuilder, channelConfig, null, sensorNo);
398     }
399
400     /**
401      * adds (or replaces) a channel and enables it within the sensor (configuration overridden)
402      *
403      * @param thingBuilder ThingBuilder of the edited thing
404      * @param channelConfig a OwChannelConfig for the new channel
405      * @param configuration the new Configuration for this channel
406      * @param sensorNo number of sensor that provides this channel
407      * @return the newly created channel
408      */
409     protected Channel addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig,
410             @Nullable Configuration configuration, int sensorNo) {
411         Channel channel = thing.getChannel(channelConfig.channelId);
412         Configuration config = configuration;
413         String label = channelConfig.label;
414
415         // remove channel if wrong type uid and preserve config if not overridden
416         if (channel != null && !channelConfig.channelTypeUID.equals(channel.getChannelTypeUID())) {
417             removeChannelIfExisting(thingBuilder, channelConfig.channelId);
418             if (config == null) {
419                 config = channel.getConfiguration();
420             }
421             channel = null;
422         }
423
424         // create channel if missing
425         if (channel == null) {
426             ChannelBuilder channelBuilder = ChannelBuilder
427                     .create(new ChannelUID(thing.getUID(), channelConfig.channelId),
428                             ACCEPTED_ITEM_TYPES_MAP.get(channelConfig.channelId))
429                     .withType(channelConfig.channelTypeUID);
430             if (label != null) {
431                 channelBuilder.withLabel(label);
432             }
433             if (config != null) {
434                 channelBuilder.withConfiguration(config);
435             }
436             channel = channelBuilder.build();
437             thingBuilder.withChannel(channel);
438         }
439
440         // enable channel in sensor
441         sensors.get(sensorNo).enableChannel(channelConfig.channelId);
442
443         return channel;
444     }
445 }