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.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.BitSet;
20 import java.util.Collections;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Queue;
25 import java.util.concurrent.ConcurrentLinkedQueue;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.onewire.internal.OwException;
32 import org.openhab.binding.onewire.internal.OwPageBuffer;
33 import org.openhab.binding.onewire.internal.SensorId;
34 import org.openhab.binding.onewire.internal.device.OwSensorType;
35 import org.openhab.binding.onewire.internal.owserver.OwfsDirectChannelConfig;
36 import org.openhab.binding.onewire.internal.owserver.OwserverConnection;
37 import org.openhab.binding.onewire.internal.owserver.OwserverConnectionState;
38 import org.openhab.binding.onewire.internal.owserver.OwserverDeviceParameter;
39 import org.openhab.core.config.core.Configuration;
40 import org.openhab.core.library.types.DecimalType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.Channel;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.binding.BaseBridgeHandler;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.State;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
56 * The {@link OwserverBridgeHandler} class implements the refresher and the interface for reading from the bridge
58 * @author Jan N. Klug - Initial contribution
61 public class OwserverBridgeHandler extends BaseBridgeHandler {
62 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_OWSERVER);
64 private final Logger logger = LoggerFactory.getLogger(OwserverBridgeHandler.class);
65 protected boolean refreshable = false;
67 protected ScheduledFuture<?> refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000,
68 TimeUnit.MILLISECONDS);
71 private final Queue<@Nullable Thing> thingPropertiesUpdateQueue = new ConcurrentLinkedQueue<>();
73 private static final int RECONNECT_AFTER_FAIL_TIME = 5000; // in ms
74 private final OwserverConnection owserverConnection;
76 private final List<OwfsDirectChannelConfig> channelConfigs = new ArrayList<>();
78 public OwserverBridgeHandler(Bridge bridge) {
80 this.owserverConnection = new OwserverConnection(this);
83 public OwserverBridgeHandler(Bridge bridge, OwserverConnection owserverConnection) {
85 this.owserverConnection = owserverConnection;
89 public void handleCommand(ChannelUID channelUID, Command command) {
93 public void initialize() {
94 Configuration configuration = getConfig();
96 if (configuration.get(CONFIG_ADDRESS) != null) {
97 owserverConnection.setHost((String) configuration.get(CONFIG_ADDRESS));
99 if (configuration.get(CONFIG_PORT) != null) {
100 owserverConnection.setPort(((BigDecimal) configuration.get(CONFIG_PORT)).intValue());
103 for (Channel channel : thing.getChannels()) {
104 if (CHANNEL_TYPE_UID_OWFS_NUMBER.equals(channel.getChannelTypeUID())
105 || CHANNEL_TYPE_UID_OWFS_STRING.equals(channel.getChannelTypeUID())) {
106 final OwfsDirectChannelConfig channelConfig = channel.getConfiguration()
107 .as(OwfsDirectChannelConfig.class);
108 if (channelConfig.initialize(channel.getUID(), channel.getAcceptedItemType())) {
109 channelConfigs.add(channelConfig);
111 logger.info("configuration mismatch: {}", channelConfig);
116 // makes it possible for unit tests to differentiate direct update and
117 // postponed update through the owserverConnection:
118 updateStatus(ThingStatus.UNKNOWN);
120 scheduler.execute(() -> {
121 synchronized (owserverConnection) {
122 owserverConnection.start();
126 if (refreshTask.isCancelled()) {
127 refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000, TimeUnit.MILLISECONDS);
132 * refresh all sensors on this bridge
134 private void refresh() {
136 long now = System.currentTimeMillis();
138 logger.trace("refresh requested by thread ID {} denied, as not refresheable",
139 Thread.currentThread().getId());
143 // refresh thing channels
144 List<Thing> thingList = getThing().getThings();
145 int thingCount = thingList.size();
146 Iterator<Thing> childListIterator = thingList.iterator();
147 logger.trace("refreshTask with thread ID {} starts at {}, {} childs", Thread.currentThread().getId(), now,
149 while (childListIterator.hasNext() && refreshable) {
150 Thing owThing = childListIterator.next();
152 logger.trace("refresh: getting handler for {} ({} to go)", owThing.getUID(), thingCount);
153 OwBaseThingHandler owHandler = (OwBaseThingHandler) owThing.getHandler();
154 if (owHandler != null) {
155 if (owHandler.isRefreshable()) {
156 logger.trace("{} initialized, refreshing", owThing.getUID());
157 owHandler.refresh(OwserverBridgeHandler.this, now);
159 logger.trace("{} not initialized, skipping refresh", owThing.getUID());
162 logger.debug("{} handler missing", owThing.getUID());
168 logger.trace("refresh aborted, as brige became non-refresheable.");
171 refreshBridgeChannels(now);
173 // update thing properties (only one per refresh cycle)
175 logger.trace("refresh aborted, as brige became non-refresheable.");
178 Thing updateThing = thingPropertiesUpdateQueue.poll();
179 if (updateThing != null) {
180 logger.trace("update: getting handler for {} ({} total in list)", updateThing.getUID(),
181 thingPropertiesUpdateQueue.size());
182 OwBaseThingHandler owHandler = (OwBaseThingHandler) updateThing.getHandler();
183 if (owHandler != null) {
185 owHandler.updateSensorProperties(this);
186 owHandler.initialize();
187 logger.debug("{} successfully updated properties, removing from property update list",
188 updateThing.getUID());
189 } catch (OwException e) {
190 thingPropertiesUpdateQueue.add(updateThing);
191 logger.debug("updating thing properties for {} failed: {}, adding to end of list",
192 updateThing.getUID(), e.getMessage());
195 logger.debug("{} is missing handler, removing from property update list", updateThing.getUID());
199 } catch (RuntimeException e) {
200 // catching RuntimeException because scheduled tasks finish once an exception occurs
201 logger.error("refresh encountered exception of {}: {}, please report bug", e.getClass(), e.getMessage());
206 public void dispose() {
208 if (!refreshTask.isCancelled()) {
209 refreshTask.cancel(false);
211 owserverConnection.stop();
215 * schedules a thing for updating the thing properties
217 * @param thing the thing to be updated
219 public void scheduleForPropertiesUpdate(Thing thing) {
220 thingPropertiesUpdateQueue.add(thing);
224 * get all sensors attached to this bridge
226 * @return a list of all sensor-IDs
228 public List<SensorId> getDirectory(String basePath) throws OwException {
229 synchronized (owserverConnection) {
230 return owserverConnection.getDirectory(basePath);
235 * check the presence of a sensor on the bus
237 * @param sensorId the sensor's full ID
238 * @return ON if present, OFF if missing
239 * @throws OwException
241 public State checkPresence(SensorId sensorId) throws OwException {
242 synchronized (owserverConnection) {
243 return owserverConnection.checkPresence(sensorId.getFullPath());
248 * get a sensors type string
250 * @param sensorId the sensor's full ID
251 * @return a String containing the sensor type
252 * @throws OwException
254 public OwSensorType getType(SensorId sensorId) throws OwException {
255 OwSensorType sensorType = OwSensorType.UNKNOWN;
256 synchronized (owserverConnection) {
258 sensorType = OwSensorType.valueOf(owserverConnection.readString(sensorId + "/type"));
259 } catch (IllegalArgumentException e) {
266 * get full sensor information stored in pages (not available on all sensors)
268 * @param sensorId the sensor's full ID
269 * @return a OwPageBuffer object containing the requested information
270 * @throws OwException
272 public OwPageBuffer readPages(SensorId sensorId) throws OwException {
273 synchronized (owserverConnection) {
274 return owserverConnection.readPages(sensorId.getFullPath());
279 * read a single decimal value from a sensor
281 * @param sensorId the sensor's full ID
282 * @param parameter device parameters needed for this request
283 * @return a DecimalType
284 * @throws OwException
286 public State readDecimalType(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
287 synchronized (owserverConnection) {
288 return owserverConnection.readDecimalType(parameter.getPath(sensorId));
293 * read a BitSet value from a sensor
295 * @param sensorId the sensor's full ID
296 * @param parameter device parameters needed for this request
298 * @throws OwException
300 public BitSet readBitSet(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
301 return BitSet.valueOf(new long[] { ((DecimalType) readDecimalType(sensorId, parameter)).longValue() });
305 * read an array of decimal values from a sensor
307 * @param sensorId the sensor's full ID
308 * @param parameter device parameters needed for this request
309 * @return a list of DecimalType values
310 * @throws OwException
312 public List<State> readDecimalTypeArray(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
313 synchronized (owserverConnection) {
314 return owserverConnection.readDecimalTypeArray(parameter.getPath(sensorId));
319 * read a string from a sensor
321 * @param sensorId the sensor's full ID
322 * @param parameter device parameters needed for this request
324 * @throws OwException
326 public String readString(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
327 synchronized (owserverConnection) {
328 return owserverConnection.readString(parameter.getPath(sensorId));
333 * writes a DecimalType to the sensor
335 * @param sensorId the sensor's full ID
336 * @param parameter device parameters needed for this request
337 * @throws OwException
339 public void writeDecimalType(SensorId sensorId, OwserverDeviceParameter parameter, DecimalType value)
341 synchronized (owserverConnection) {
342 owserverConnection.writeDecimalType(parameter.getPath(sensorId), value);
347 * writes a BitSet to the sensor
349 * @param sensorId the sensor's full ID
350 * @param parameter device parameters needed for this request
351 * @throws OwException
353 public void writeBitSet(SensorId sensorId, OwserverDeviceParameter parameter, BitSet value) throws OwException {
354 writeDecimalType(sensorId, parameter, new DecimalType(value.toLongArray()[0]));
358 * returns if this bridge is refreshable
360 * @return true if implementation reports communication ready
362 public boolean isRefreshable() {
367 * updates the thing status with the current connection state
369 * @param connectionState current connection state
371 public void reportConnectionState(OwserverConnectionState connectionState) {
372 logger.debug("Updating owserverconnectionstate to {}", connectionState);
373 switch (connectionState) {
376 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
377 scheduler.schedule(() -> {
378 synchronized (owserverConnection) {
379 owserverConnection.start();
381 }, RECONNECT_AFTER_FAIL_TIME, TimeUnit.MILLISECONDS);
389 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
395 * refreshes channels attached to the bridge
397 * @param now current time
399 public void refreshBridgeChannels(long now) {
400 for (OwfsDirectChannelConfig channelConfig : channelConfigs) {
401 if (now > channelConfig.lastRefresh + channelConfig.refreshCycle) {
404 synchronized (owserverConnection) {
405 if (channelConfig.acceptedItemType.equals("String")) {
406 value = new StringType(owserverConnection.readString(channelConfig.path));
407 } else if (channelConfig.acceptedItemType.equals("Number")) {
408 value = owserverConnection.readDecimalType(channelConfig.path);
410 logger.debug("mismatched configuration, itemType unknown for channel {}",
411 channelConfig.channelUID);
416 final ChannelUID channelUID = channelConfig.channelUID;
417 if (channelUID == null) {
418 throw new OwException("channelUID is null");
420 updateState(channelUID, value);
421 logger.trace("updated {} to {}", channelConfig.channelUID, value);
423 channelConfig.lastRefresh = now;
424 } catch (OwException e) {
425 logger.debug("could not read direct channel {}: {}", channelConfig.channelUID, e.getMessage());