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.Collection;
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.discovery.OwDiscoveryService;
36 import org.openhab.binding.onewire.internal.owserver.OwfsDirectChannelConfig;
37 import org.openhab.binding.onewire.internal.owserver.OwserverConnection;
38 import org.openhab.binding.onewire.internal.owserver.OwserverConnectionState;
39 import org.openhab.binding.onewire.internal.owserver.OwserverDeviceParameter;
40 import org.openhab.core.config.core.Configuration;
41 import org.openhab.core.library.types.DecimalType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.thing.Bridge;
44 import org.openhab.core.thing.Channel;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.thing.ThingTypeUID;
50 import org.openhab.core.thing.binding.BaseBridgeHandler;
51 import org.openhab.core.thing.binding.ThingHandlerService;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.State;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * The {@link OwserverBridgeHandler} class implements the refresher and the interface for reading from the bridge
60 * @author Jan N. Klug - Initial contribution
63 public class OwserverBridgeHandler extends BaseBridgeHandler {
64 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_OWSERVER);
66 private final Logger logger = LoggerFactory.getLogger(OwserverBridgeHandler.class);
67 protected boolean refreshable = false;
69 protected ScheduledFuture<?> refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000,
70 TimeUnit.MILLISECONDS);
73 private final Queue<@Nullable Thing> thingPropertiesUpdateQueue = new ConcurrentLinkedQueue<>();
75 private static final int RECONNECT_AFTER_FAIL_TIME = 5000; // in ms
76 private final OwserverConnection owserverConnection;
78 private final List<OwfsDirectChannelConfig> channelConfigs = new ArrayList<>();
80 public OwserverBridgeHandler(Bridge bridge) {
82 this.owserverConnection = new OwserverConnection(this);
85 public OwserverBridgeHandler(Bridge bridge, OwserverConnection owserverConnection) {
87 this.owserverConnection = owserverConnection;
91 public void handleCommand(ChannelUID channelUID, Command command) {
95 public void initialize() {
96 Configuration configuration = getConfig();
98 if (configuration.get(CONFIG_ADDRESS) != null) {
99 owserverConnection.setHost((String) configuration.get(CONFIG_ADDRESS));
101 if (configuration.get(CONFIG_PORT) != null) {
102 owserverConnection.setPort(((BigDecimal) configuration.get(CONFIG_PORT)).intValue());
105 for (Channel channel : thing.getChannels()) {
106 if (CHANNEL_TYPE_UID_OWFS_NUMBER.equals(channel.getChannelTypeUID())
107 || CHANNEL_TYPE_UID_OWFS_STRING.equals(channel.getChannelTypeUID())) {
108 final OwfsDirectChannelConfig channelConfig = channel.getConfiguration()
109 .as(OwfsDirectChannelConfig.class);
110 if (channelConfig.initialize(channel.getUID(), channel.getAcceptedItemType())) {
111 channelConfigs.add(channelConfig);
113 logger.info("configuration mismatch: {}", channelConfig);
118 // makes it possible for unit tests to differentiate direct update and
119 // postponed update through the owserverConnection:
120 updateStatus(ThingStatus.UNKNOWN);
122 scheduler.execute(() -> {
123 synchronized (owserverConnection) {
124 owserverConnection.start();
128 if (refreshTask.isCancelled()) {
129 refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000, TimeUnit.MILLISECONDS);
134 * refresh all sensors on this bridge
136 private void refresh() {
138 long now = System.currentTimeMillis();
140 logger.trace("refresh requested by thread ID {} denied, as not refresheable",
141 Thread.currentThread().getId());
145 // refresh thing channels
146 List<Thing> thingList = getThing().getThings();
147 int thingCount = thingList.size();
148 Iterator<Thing> childListIterator = thingList.iterator();
149 logger.trace("refreshTask with thread ID {} starts at {}, {} childs", Thread.currentThread().getId(), now,
151 while (childListIterator.hasNext() && refreshable) {
152 Thing owThing = childListIterator.next();
154 logger.trace("refresh: getting handler for {} ({} to go)", owThing.getUID(), thingCount);
155 OwBaseThingHandler owHandler = (OwBaseThingHandler) owThing.getHandler();
156 if (owHandler != null) {
157 if (owHandler.isRefreshable()) {
158 logger.trace("{} initialized, refreshing", owThing.getUID());
159 owHandler.refresh(OwserverBridgeHandler.this, now);
161 logger.trace("{} not initialized, skipping refresh", owThing.getUID());
164 logger.debug("{} handler missing", owThing.getUID());
170 logger.trace("refresh aborted, as brige became non-refresheable.");
173 refreshBridgeChannels(now);
175 // update thing properties (only one per refresh cycle)
177 logger.trace("refresh aborted, as brige became non-refresheable.");
180 Thing updateThing = thingPropertiesUpdateQueue.poll();
181 if (updateThing != null) {
182 logger.trace("update: getting handler for {} ({} total in list)", updateThing.getUID(),
183 thingPropertiesUpdateQueue.size());
184 OwBaseThingHandler owHandler = (OwBaseThingHandler) updateThing.getHandler();
185 if (owHandler != null) {
187 owHandler.updateSensorProperties(this);
188 owHandler.initialize();
189 logger.debug("{} successfully updated properties, removing from property update list",
190 updateThing.getUID());
191 } catch (OwException e) {
192 thingPropertiesUpdateQueue.add(updateThing);
193 logger.debug("updating thing properties for {} failed: {}, adding to end of list",
194 updateThing.getUID(), e.getMessage());
197 logger.debug("{} is missing handler, removing from property update list", updateThing.getUID());
201 } catch (RuntimeException e) {
202 // catching RuntimeException because scheduled tasks finish once an exception occurs
203 logger.error("refresh encountered exception of {}: {}, please report bug", e.getClass(), e.getMessage());
208 public void dispose() {
210 if (!refreshTask.isCancelled()) {
211 refreshTask.cancel(false);
213 owserverConnection.stop();
217 * schedules a thing for updating the thing properties
219 * @param thing the thing to be updated
221 public void scheduleForPropertiesUpdate(Thing thing) {
222 thingPropertiesUpdateQueue.add(thing);
226 * get all sensors attached to this bridge
228 * @return a list of all sensor-IDs
230 public List<SensorId> getDirectory(String basePath) throws OwException {
231 synchronized (owserverConnection) {
232 return owserverConnection.getDirectory(basePath);
237 * check the presence of a sensor on the bus
239 * @param sensorId the sensor's full ID
240 * @return ON if present, OFF if missing
241 * @throws OwException in case an error occurs
243 public State checkPresence(SensorId sensorId) throws OwException {
244 synchronized (owserverConnection) {
245 return owserverConnection.checkPresence(sensorId.getFullPath());
250 * get a sensors type string
252 * @param sensorId the sensor's full ID
253 * @return a String containing the sensor type
254 * @throws OwException in case an error occurs
256 public OwSensorType getType(SensorId sensorId) throws OwException {
257 OwSensorType sensorType = OwSensorType.UNKNOWN;
258 synchronized (owserverConnection) {
260 sensorType = OwSensorType.valueOf(owserverConnection.readString(sensorId + "/type"));
261 } catch (IllegalArgumentException ignored) {
268 * get full sensor information stored in pages (not available on all sensors)
270 * @param sensorId the sensor's full ID
271 * @return a OwPageBuffer object containing the requested information
272 * @throws OwException in case an error occurs
274 public OwPageBuffer readPages(SensorId sensorId) throws OwException {
275 synchronized (owserverConnection) {
276 return owserverConnection.readPages(sensorId.getFullPath());
281 * read a single decimal value from a sensor
283 * @param sensorId the sensor's full ID
284 * @param parameter device parameters needed for this request
285 * @return a DecimalType
286 * @throws OwException in case an error occurs
288 public State readDecimalType(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
289 synchronized (owserverConnection) {
290 return owserverConnection.readDecimalType(parameter.getPath(sensorId));
295 * read a BitSet value from a sensor
297 * @param sensorId the sensor's full ID
298 * @param parameter device parameters needed for this request
300 * @throws OwException in case an error occurs
302 public BitSet readBitSet(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
303 return BitSet.valueOf(new long[] { ((DecimalType) readDecimalType(sensorId, parameter)).longValue() });
307 * read an array of decimal values from a sensor
309 * @param sensorId the sensor's full ID
310 * @param parameter device parameters needed for this request
311 * @return a list of DecimalType values
312 * @throws OwException in case an error occurs
314 public List<State> readDecimalTypeArray(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
315 synchronized (owserverConnection) {
316 return owserverConnection.readDecimalTypeArray(parameter.getPath(sensorId));
321 * read a string from a sensor
323 * @param sensorId the sensor's full ID
324 * @param parameter device parameters needed for this request
326 * @throws OwException in case an error occurs
328 public String readString(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
329 synchronized (owserverConnection) {
330 return owserverConnection.readString(parameter.getPath(sensorId));
335 * writes a DecimalType to the sensor
337 * @param sensorId the sensor's full ID
338 * @param parameter device parameters needed for this request
339 * @throws OwException in case an error occurs
341 public void writeDecimalType(SensorId sensorId, OwserverDeviceParameter parameter, DecimalType value)
343 synchronized (owserverConnection) {
344 owserverConnection.writeDecimalType(parameter.getPath(sensorId), value);
349 * writes a BitSet to the sensor
351 * @param sensorId the sensor's full ID
352 * @param parameter device parameters needed for this request
353 * @throws OwException in case an error occurs
355 public void writeBitSet(SensorId sensorId, OwserverDeviceParameter parameter, BitSet value) throws OwException {
356 writeDecimalType(sensorId, parameter, new DecimalType(value.toLongArray()[0]));
360 * returns if this bridge is refreshable
362 * @return true if implementation reports communication ready
364 public boolean isRefreshable() {
369 * updates the thing status with the current connection state
371 * @param connectionState current connection state
373 public void reportConnectionState(OwserverConnectionState connectionState) {
374 logger.debug("Updating owserverconnectionstate to {}", connectionState);
375 switch (connectionState) {
378 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
379 scheduler.schedule(() -> {
380 synchronized (owserverConnection) {
381 owserverConnection.start();
383 }, RECONNECT_AFTER_FAIL_TIME, TimeUnit.MILLISECONDS);
391 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
397 * refreshes channels attached to the bridge
399 * @param now current time
401 public void refreshBridgeChannels(long now) {
402 for (OwfsDirectChannelConfig channelConfig : channelConfigs) {
403 if (now > channelConfig.lastRefresh + channelConfig.refreshCycle) {
406 synchronized (owserverConnection) {
407 if ("String".equals(channelConfig.acceptedItemType)) {
408 value = new StringType(owserverConnection.readString(channelConfig.path));
409 } else if ("Number".equals(channelConfig.acceptedItemType)) {
410 value = owserverConnection.readDecimalType(channelConfig.path);
412 logger.debug("mismatched configuration, itemType unknown for channel {}",
413 channelConfig.channelUID);
418 final ChannelUID channelUID = channelConfig.channelUID;
419 if (channelUID == null) {
420 throw new OwException("channelUID is null");
422 updateState(channelUID, value);
423 logger.trace("updated {} to {}", channelConfig.channelUID, value);
425 channelConfig.lastRefresh = now;
426 } catch (OwException e) {
427 logger.debug("could not read direct channel {}: {}", channelConfig.channelUID, e.getMessage());
434 public Collection<Class<? extends ThingHandlerService>> getServices() {
435 return Set.of(OwDiscoveryService.class);