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