2 * Copyright (c) 2010-2023 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.onewire.internal.handler;
15 import static org.openhab.binding.onewire.internal.OwBindingConstants.*;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.List;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
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;
56 * The {@link OwBaseThingHandler} class defines a handler for simple OneWire devices
58 * @author Jan N. Klug - Initial contribution
61 public abstract class OwBaseThingHandler extends BaseThingHandler {
62 private final Logger logger = LoggerFactory.getLogger(OwBaseThingHandler.class);
64 protected static final int PROPERTY_UPDATE_INTERVAL = 5000; // in ms
65 protected static final int PROPERTY_UPDATE_MAX_RETRY = 5;
67 private static final Set<String> REQUIRED_PROPERTIES = Collections
68 .unmodifiableSet(Stream.of(PROPERTY_MODELID, PROPERTY_VENDOR).collect(Collectors.toSet()));
70 protected List<String> requiredProperties = new ArrayList<>(REQUIRED_PROPERTIES);
71 protected Set<OwSensorType> supportedSensorTypes;
73 protected final List<AbstractOwDevice> sensors = new ArrayList<>();
74 protected @NonNullByDefault({}) SensorId sensorId;
75 protected @NonNullByDefault({}) OwSensorType sensorType;
77 protected long lastRefresh = 0;
78 protected long refreshInterval = 300 * 1000;
80 protected boolean validConfig = false;
81 protected boolean showPresence = false;
83 protected OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider;
85 protected @Nullable ScheduledFuture<?> updateTask;
87 public OwBaseThingHandler(Thing thing, OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider,
88 Set<OwSensorType> supportedSensorTypes) {
91 this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider;
92 this.supportedSensorTypes = supportedSensorTypes;
95 public OwBaseThingHandler(Thing thing, OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider,
96 Set<OwSensorType> supportedSensorTypes, Set<String> requiredProperties) {
99 this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider;
100 this.supportedSensorTypes = supportedSensorTypes;
101 this.requiredProperties.addAll(requiredProperties);
105 public void handleCommand(ChannelUID channelUID, Command command) {
106 if (command instanceof RefreshType) {
108 logger.trace("scheduled {} for refresh", this.thing.getUID());
113 public void initialize() {
114 configureThingHandler();
117 protected boolean configureThingHandler() {
118 BaseHandlerConfiguration configuration = getConfig().as(BaseHandlerConfiguration.class);
119 Map<String, String> properties = thing.getProperties();
121 if (getBridge() == null) {
122 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge missing");
127 final String id = configuration.id;
130 this.sensorId = new SensorId(id);
131 } catch (IllegalArgumentException e) {
132 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "sensor id format mismatch");
136 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "sensor id missing");
140 refreshInterval = configuration.refresh * 1000;
142 // check if all required properties are present. update if not
143 for (String property : requiredProperties) {
144 if (!properties.containsKey(property)) {
145 updateSensorProperties();
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");
161 protected void configureThingChannels() {
162 ThingBuilder thingBuilder = editThing();
164 logger.debug("configuring sensors for {}", thing.getUID());
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));
174 // add or update wanted channels
175 SENSOR_TYPE_CHANNEL_MAP.get(sensorType).stream().forEach(channelConfig -> {
176 addChannelIfMissingAndEnable(thingBuilder, channelConfig);
179 updateThing(thingBuilder.build());
182 sensors.get(0).configureChannels();
183 } catch (OwException e) {
184 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
188 if (thing.getChannel(CHANNEL_PRESENT) != null) {
193 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
197 * check if thing can be refreshed from the bridge handler
199 * @return true if thing can be refreshed
201 public boolean isRefreshable() {
202 return super.isInitialized()
203 && this.thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR
204 && this.thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.BRIDGE_OFFLINE;
210 * needs proper exception handling for refresh errors if overridden
212 * @param bridgeHandler bridge handler to use for communication with ow bus
213 * @param now current time
215 public void refresh(OwserverBridgeHandler bridgeHandler, long now) {
217 Boolean forcedRefresh = lastRefresh == 0;
218 if (now >= (lastRefresh + refreshInterval)) {
219 logger.trace("refreshing {}", this.thing.getUID());
223 if (!sensors.get(0).checkPresence(bridgeHandler)) {
224 logger.trace("sensor not present");
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);
233 } catch (OwException e) {
234 logger.debug("{}: refresh exception {}", this.thing.getUID(), e.getMessage());
235 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "refresh exception");
240 * update presence status to present state of slave
242 * @param presentState current present state
244 public void updatePresenceStatus(State presentState) {
245 if (OnOffType.ON.equals(presentState)) {
246 updateStatus(ThingStatus.ONLINE);
248 updateState(CHANNEL_PRESENT, OnOffType.ON);
250 } else if (OnOffType.OFF.equals(presentState)) {
251 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "slave missing");
253 updateState(CHANNEL_PRESENT, OnOffType.OFF);
256 updateStatus(ThingStatus.UNKNOWN);
258 updateState(CHANNEL_PRESENT, UnDefType.UNDEF);
264 * post update to channel
266 * @param channelId channel id
267 * @param state new channel state
269 public void postUpdate(String channelId, State state) {
270 if (this.thing.getChannel(channelId) != null) {
271 updateState(channelId, state);
273 logger.warn("{} missing channel {} when posting update {}", this.thing.getUID(), channelId, state);
278 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
279 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
280 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
282 updatePresenceStatus(UnDefType.UNDEF);
284 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
286 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
287 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
292 public void dispose() {
293 dynamicStateDescriptionProvider.removeDescriptionsForThing(thing.getUID());
298 * add this sensor to the property update list of the bridge handler
301 protected void updateSensorProperties() {
302 Bridge bridge = getBridge();
303 if (bridge == null) {
304 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge not found");
308 OwserverBridgeHandler bridgeHandler = (OwserverBridgeHandler) bridge.getHandler();
309 if (bridgeHandler == null) {
310 logger.debug("bridgehandler for {} not available for scheduling property update, retrying in 5s",
312 scheduler.schedule(() -> {
313 updateSensorProperties();
314 }, 5000, TimeUnit.MILLISECONDS);
318 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "required properties missing");
319 bridgeHandler.scheduleForPropertiesUpdate(thing);
323 * thing specific update method for sensor properties
325 * called by the bridge handler
327 * @param bridgeHandler the bridge handler to be used
328 * @return properties to be added to the properties map
329 * @throws OwException
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");
337 updateProperties(properties);
339 logger.trace("updated modelid/vendor to {} / {}", sensorType.name(), "Dallas/Maxim");
343 * get the dynamic state description provider for this thing
347 public @Nullable OwDynamicStateDescriptionProvider getDynamicStateDescriptionProvider() {
348 return dynamicStateDescriptionProvider;
352 * remove a channel during initialization if it exists
354 * @param thingBuilder ThingBuilder of the edited thing
355 * @param channelId id of the channel
357 protected void removeChannelIfExisting(ThingBuilder thingBuilder, String channelId) {
358 if (thing.getChannel(channelId) != null) {
359 thingBuilder.withoutChannel(new ChannelUID(thing.getUID(), channelId));
364 * adds (or replaces) a channel and enables it within the sensor (configuration preserved, default sensor)
366 * @param thingBuilder ThingBuilder of the edited thing
367 * @param channelConfig a OwChannelConfig for the new channel
368 * @return the newly created channel
370 protected Channel addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig) {
371 return addChannelIfMissingAndEnable(thingBuilder, channelConfig, null, 0);
375 * adds (or replaces) a channel and enables it within the sensor (configuration overridden, default sensor)
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
382 protected Channel addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig,
383 Configuration configuration) {
384 return addChannelIfMissingAndEnable(thingBuilder, channelConfig, configuration, 0);
388 * adds (or replaces) a channel and enables it within the sensor (configuration preserved)
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
395 protected Channel addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig,
397 return addChannelIfMissingAndEnable(thingBuilder, channelConfig, null, sensorNo);
401 * adds (or replaces) a channel and enables it within the sensor (configuration overridden)
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
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;
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();
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);
431 channelBuilder.withLabel(label);
433 if (config != null) {
434 channelBuilder.withConfiguration(config);
436 channel = channelBuilder.build();
437 thingBuilder.withChannel(channel);
440 // enable channel in sensor
441 sensors.get(sensorNo).enableChannel(channelConfig.channelId);