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.List;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
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;
55 * The {@link OwBaseThingHandler} class defines a handler for simple OneWire devices
57 * @author Jan N. Klug - Initial contribution
60 public abstract class OwBaseThingHandler extends BaseThingHandler {
61 private final Logger logger = LoggerFactory.getLogger(OwBaseThingHandler.class);
63 protected static final int PROPERTY_UPDATE_INTERVAL = 5000; // in ms
64 protected static final int PROPERTY_UPDATE_MAX_RETRY = 5;
66 private static final Set<String> REQUIRED_PROPERTIES = Set.of(PROPERTY_MODELID, PROPERTY_VENDOR);
68 protected List<String> requiredProperties = new ArrayList<>(REQUIRED_PROPERTIES);
69 protected Set<OwSensorType> supportedSensorTypes;
71 protected final List<AbstractOwDevice> sensors = new ArrayList<>();
72 protected @NonNullByDefault({}) SensorId sensorId;
73 protected @NonNullByDefault({}) OwSensorType sensorType;
75 protected long lastRefresh = 0;
76 protected long refreshInterval = 300 * 1000;
78 protected boolean validConfig = false;
79 protected boolean showPresence = false;
81 protected OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider;
83 protected @Nullable ScheduledFuture<?> updateTask;
85 public OwBaseThingHandler(Thing thing, OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider,
86 Set<OwSensorType> supportedSensorTypes) {
89 this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider;
90 this.supportedSensorTypes = supportedSensorTypes;
93 public OwBaseThingHandler(Thing thing, OwDynamicStateDescriptionProvider dynamicStateDescriptionProvider,
94 Set<OwSensorType> supportedSensorTypes, Set<String> requiredProperties) {
97 this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider;
98 this.supportedSensorTypes = supportedSensorTypes;
99 this.requiredProperties.addAll(requiredProperties);
103 public void handleCommand(ChannelUID channelUID, Command command) {
104 if (command instanceof RefreshType) {
106 logger.trace("scheduled {} for refresh", this.thing.getUID());
111 public void initialize() {
112 configureThingHandler();
115 protected boolean configureThingHandler() {
116 BaseHandlerConfiguration configuration = getConfig().as(BaseHandlerConfiguration.class);
117 Map<String, String> properties = thing.getProperties();
119 if (getBridge() == null) {
120 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge missing");
125 final String id = configuration.id;
128 this.sensorId = new SensorId(id);
129 } catch (IllegalArgumentException e) {
130 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "sensor id format mismatch");
134 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "sensor id missing");
138 refreshInterval = configuration.refresh * 1000L;
140 // check if all required properties are present. update if not
141 for (String property : requiredProperties) {
142 if (!properties.containsKey(property)) {
143 updateSensorProperties();
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");
159 protected void configureThingChannels() {
160 ThingBuilder thingBuilder = editThing();
162 logger.debug("configuring sensors for {}", thing.getUID());
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));
172 // add or update wanted channels
173 SENSOR_TYPE_CHANNEL_MAP.getOrDefault(sensorType, Set.of()).stream().forEach(channelConfig -> {
174 addChannelIfMissingAndEnable(thingBuilder, channelConfig);
177 updateThing(thingBuilder.build());
180 sensors.get(0).configureChannels();
181 } catch (OwException e) {
182 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
186 if (thing.getChannel(CHANNEL_PRESENT) != null) {
191 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
195 * check if thing can be refreshed from the bridge handler
197 * @return true if thing can be refreshed
199 public boolean isRefreshable() {
200 return super.isInitialized()
201 && this.thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR
202 && this.thing.getStatusInfo().getStatusDetail() != ThingStatusDetail.BRIDGE_OFFLINE;
208 * needs proper exception handling for refresh errors if overridden
210 * @param bridgeHandler bridge handler to use for communication with ow bus
211 * @param now current time
213 public void refresh(OwserverBridgeHandler bridgeHandler, long now) {
215 Boolean forcedRefresh = lastRefresh == 0;
216 if (now >= (lastRefresh + refreshInterval)) {
217 logger.trace("refreshing {}", this.thing.getUID());
221 if (!sensors.get(0).checkPresence(bridgeHandler)) {
222 logger.trace("sensor not present");
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);
231 } catch (OwException e) {
232 logger.debug("{}: refresh exception {}", this.thing.getUID(), e.getMessage());
233 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "refresh exception");
238 * update presence status to present state of slave
240 * @param presentState current present state
242 public void updatePresenceStatus(State presentState) {
243 if (OnOffType.ON.equals(presentState)) {
244 updateStatus(ThingStatus.ONLINE);
246 updateState(CHANNEL_PRESENT, OnOffType.ON);
248 } else if (OnOffType.OFF.equals(presentState)) {
249 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "slave missing");
251 updateState(CHANNEL_PRESENT, OnOffType.OFF);
254 updateStatus(ThingStatus.UNKNOWN);
256 updateState(CHANNEL_PRESENT, UnDefType.UNDEF);
262 * post update to channel
264 * @param channelId channel id
265 * @param state new channel state
267 public void postUpdate(String channelId, State state) {
268 if (this.thing.getChannel(channelId) != null) {
269 updateState(channelId, state);
271 logger.warn("{} missing channel {} when posting update {}", this.thing.getUID(), channelId, state);
276 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
277 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
278 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
280 updatePresenceStatus(UnDefType.UNDEF);
282 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
284 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
285 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
290 public void dispose() {
291 dynamicStateDescriptionProvider.removeDescriptionsForThing(thing.getUID());
296 * add this sensor to the property update list of the bridge handler
299 protected void updateSensorProperties() {
300 Bridge bridge = getBridge();
301 if (bridge == null) {
302 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge not found");
306 OwserverBridgeHandler bridgeHandler = (OwserverBridgeHandler) bridge.getHandler();
307 if (bridgeHandler == null) {
308 logger.debug("bridgehandler for {} not available for scheduling property update, retrying in 5s",
310 scheduler.schedule(() -> {
311 updateSensorProperties();
312 }, 5000, TimeUnit.MILLISECONDS);
316 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "required properties missing");
317 bridgeHandler.scheduleForPropertiesUpdate(thing);
321 * thing specific update method for sensor properties
323 * called by the bridge handler
325 * @param bridgeHandler the bridge handler to be used
326 * @throws OwException in case an error occurs
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");
334 updateProperties(properties);
336 logger.trace("updated modelid/vendor to {} / {}", sensorType.name(), "Dallas/Maxim");
340 * get the dynamic state description provider for this thing
344 public @Nullable OwDynamicStateDescriptionProvider getDynamicStateDescriptionProvider() {
345 return dynamicStateDescriptionProvider;
349 * remove a channel during initialization if it exists
351 * @param thingBuilder ThingBuilder of the edited thing
352 * @param channelId id of the channel
354 protected void removeChannelIfExisting(ThingBuilder thingBuilder, String channelId) {
355 if (thing.getChannel(channelId) != null) {
356 thingBuilder.withoutChannel(new ChannelUID(thing.getUID(), channelId));
361 * adds (or replaces) a channel and enables it within the sensor (configuration preserved, default sensor)
363 * @param thingBuilder ThingBuilder of the edited thing
364 * @param channelConfig a OwChannelConfig for the new channel
366 protected void addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig) {
367 addChannelIfMissingAndEnable(thingBuilder, channelConfig, null, 0);
371 * adds (or replaces) a channel and enables it within the sensor (configuration overridden, default sensor)
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
377 protected void addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig,
378 Configuration configuration) {
379 addChannelIfMissingAndEnable(thingBuilder, channelConfig, configuration, 0);
383 * adds (or replaces) a channel and enables it within the sensor (configuration preserved)
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
389 protected void addChannelIfMissingAndEnable(ThingBuilder thingBuilder, OwChannelConfig channelConfig,
391 addChannelIfMissingAndEnable(thingBuilder, channelConfig, null, sensorNo);
395 * adds (or replaces) a channel and enables it within the sensor (configuration overridden)
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
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;
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();
417 // create channel if missing
418 if (channel == null) {
419 ChannelUID channelUID = new ChannelUID(thing.getUID(), channelConfig.channelId);
421 ThingHandlerCallback callback = getCallback();
422 if (callback == null) {
423 logger.warn("Could not get callback, adding '{}' failed.", channelUID);
427 ChannelBuilder channelBuilder = callback.createChannelBuilder(channelUID, channelConfig.channelTypeUID);
430 channelBuilder.withLabel(label);
432 if (config != null) {
433 channelBuilder.withConfiguration(config);
436 channel = channelBuilder.build();
437 thingBuilder.withChannel(channel);
440 // enable channel in sensor
441 sensors.get(sensorNo).enableChannel(channelConfig.channelId);