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.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Queue;
26 import java.util.concurrent.ConcurrentLinkedQueue;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.onewire.internal.OwException;
33 import org.openhab.binding.onewire.internal.OwPageBuffer;
34 import org.openhab.binding.onewire.internal.SensorId;
35 import org.openhab.binding.onewire.internal.device.OwSensorType;
36 import org.openhab.binding.onewire.internal.discovery.OwDiscoveryService;
37 import org.openhab.binding.onewire.internal.owserver.OwfsDirectChannelConfig;
38 import org.openhab.binding.onewire.internal.owserver.OwserverConnection;
39 import org.openhab.binding.onewire.internal.owserver.OwserverConnectionState;
40 import org.openhab.binding.onewire.internal.owserver.OwserverDeviceParameter;
41 import org.openhab.core.config.core.Configuration;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.thing.Bridge;
45 import org.openhab.core.thing.Channel;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.ThingTypeUID;
51 import org.openhab.core.thing.binding.BaseBridgeHandler;
52 import org.openhab.core.thing.binding.ThingHandlerService;
53 import org.openhab.core.types.Command;
54 import org.openhab.core.types.State;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * The {@link OwserverBridgeHandler} class implements the refresher and the interface for reading from the bridge
61 * @author Jan N. Klug - Initial contribution
64 public class OwserverBridgeHandler extends BaseBridgeHandler {
65 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_OWSERVER);
67 private final Logger logger = LoggerFactory.getLogger(OwserverBridgeHandler.class);
68 protected boolean refreshable = false;
70 protected ScheduledFuture<?> refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000,
71 TimeUnit.MILLISECONDS);
74 private final Queue<@Nullable Thing> thingPropertiesUpdateQueue = new ConcurrentLinkedQueue<>();
76 private static final int RECONNECT_AFTER_FAIL_TIME = 5000; // in ms
77 private final OwserverConnection owserverConnection;
79 private final List<OwfsDirectChannelConfig> channelConfigs = new ArrayList<>();
81 public OwserverBridgeHandler(Bridge bridge) {
83 this.owserverConnection = new OwserverConnection(this);
86 public OwserverBridgeHandler(Bridge bridge, OwserverConnection owserverConnection) {
88 this.owserverConnection = owserverConnection;
92 public void handleCommand(ChannelUID channelUID, Command command) {
96 public void initialize() {
97 Configuration configuration = getConfig();
99 if (configuration.get(CONFIG_ADDRESS) != null) {
100 owserverConnection.setHost((String) configuration.get(CONFIG_ADDRESS));
102 if (configuration.get(CONFIG_PORT) != null) {
103 owserverConnection.setPort(((BigDecimal) configuration.get(CONFIG_PORT)).intValue());
106 for (Channel channel : thing.getChannels()) {
107 if (CHANNEL_TYPE_UID_OWFS_NUMBER.equals(channel.getChannelTypeUID())
108 || CHANNEL_TYPE_UID_OWFS_STRING.equals(channel.getChannelTypeUID())) {
109 final OwfsDirectChannelConfig channelConfig = channel.getConfiguration()
110 .as(OwfsDirectChannelConfig.class);
111 if (channelConfig.initialize(channel.getUID(), channel.getAcceptedItemType())) {
112 channelConfigs.add(channelConfig);
114 logger.info("configuration mismatch: {}", channelConfig);
119 // makes it possible for unit tests to differentiate direct update and
120 // postponed update through the owserverConnection:
121 updateStatus(ThingStatus.UNKNOWN);
123 scheduler.execute(() -> {
124 synchronized (owserverConnection) {
125 owserverConnection.start();
129 if (refreshTask.isCancelled()) {
130 refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000, TimeUnit.MILLISECONDS);
135 * refresh all sensors on this bridge
137 private void refresh() {
139 long now = System.currentTimeMillis();
141 logger.trace("refresh requested by thread ID {} denied, as not refresheable",
142 Thread.currentThread().getId());
146 // refresh thing channels
147 List<Thing> thingList = getThing().getThings();
148 int thingCount = thingList.size();
149 Iterator<Thing> childListIterator = thingList.iterator();
150 logger.trace("refreshTask with thread ID {} starts at {}, {} childs", Thread.currentThread().getId(), now,
152 while (childListIterator.hasNext() && refreshable) {
153 Thing owThing = childListIterator.next();
155 logger.trace("refresh: getting handler for {} ({} to go)", owThing.getUID(), thingCount);
156 OwBaseThingHandler owHandler = (OwBaseThingHandler) owThing.getHandler();
157 if (owHandler != null) {
158 if (owHandler.isRefreshable()) {
159 logger.trace("{} initialized, refreshing", owThing.getUID());
160 owHandler.refresh(OwserverBridgeHandler.this, now);
162 logger.trace("{} not initialized, skipping refresh", owThing.getUID());
165 logger.debug("{} handler missing", owThing.getUID());
171 logger.trace("refresh aborted, as brige became non-refresheable.");
174 refreshBridgeChannels(now);
176 // update thing properties (only one per refresh cycle)
178 logger.trace("refresh aborted, as brige became non-refresheable.");
181 Thing updateThing = thingPropertiesUpdateQueue.poll();
182 if (updateThing != null) {
183 logger.trace("update: getting handler for {} ({} total in list)", updateThing.getUID(),
184 thingPropertiesUpdateQueue.size());
185 OwBaseThingHandler owHandler = (OwBaseThingHandler) updateThing.getHandler();
186 if (owHandler != null) {
188 owHandler.updateSensorProperties(this);
189 owHandler.initialize();
190 logger.debug("{} successfully updated properties, removing from property update list",
191 updateThing.getUID());
192 } catch (OwException e) {
193 thingPropertiesUpdateQueue.add(updateThing);
194 logger.debug("updating thing properties for {} failed: {}, adding to end of list",
195 updateThing.getUID(), e.getMessage());
198 logger.debug("{} is missing handler, removing from property update list", updateThing.getUID());
202 } catch (RuntimeException e) {
203 // catching RuntimeException because scheduled tasks finish once an exception occurs
204 logger.error("refresh encountered exception of {}: {}, please report bug", e.getClass(), e.getMessage());
209 public void dispose() {
211 if (!refreshTask.isCancelled()) {
212 refreshTask.cancel(false);
214 owserverConnection.stop();
218 * schedules a thing for updating the thing properties
220 * @param thing the thing to be updated
222 public void scheduleForPropertiesUpdate(Thing thing) {
223 thingPropertiesUpdateQueue.add(thing);
227 * get all sensors attached to this bridge
229 * @return a list of all sensor-IDs
231 public List<SensorId> getDirectory(String basePath) throws OwException {
232 synchronized (owserverConnection) {
233 return owserverConnection.getDirectory(basePath);
238 * check the presence of a sensor on the bus
240 * @param sensorId the sensor's full ID
241 * @return ON if present, OFF if missing
242 * @throws OwException in case an error occurs
244 public State checkPresence(SensorId sensorId) throws OwException {
245 synchronized (owserverConnection) {
246 return owserverConnection.checkPresence(sensorId.getFullPath());
251 * get a sensors type string
253 * @param sensorId the sensor's full ID
254 * @return a String containing the sensor type
255 * @throws OwException in case an error occurs
257 public OwSensorType getType(SensorId sensorId) throws OwException {
258 OwSensorType sensorType = OwSensorType.UNKNOWN;
259 synchronized (owserverConnection) {
261 sensorType = OwSensorType.valueOf(owserverConnection.readString(sensorId + "/type"));
262 } catch (IllegalArgumentException ignored) {
269 * get full sensor information stored in pages (not available on all sensors)
271 * @param sensorId the sensor's full ID
272 * @return a OwPageBuffer object containing the requested information
273 * @throws OwException in case an error occurs
275 public OwPageBuffer readPages(SensorId sensorId) throws OwException {
276 synchronized (owserverConnection) {
277 return owserverConnection.readPages(sensorId.getFullPath());
282 * read a single decimal value from a sensor
284 * @param sensorId the sensor's full ID
285 * @param parameter device parameters needed for this request
286 * @return a DecimalType
287 * @throws OwException in case an error occurs
289 public State readDecimalType(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
290 synchronized (owserverConnection) {
291 return owserverConnection.readDecimalType(parameter.getPath(sensorId));
296 * read a BitSet value from a sensor
298 * @param sensorId the sensor's full ID
299 * @param parameter device parameters needed for this request
301 * @throws OwException in case an error occurs
303 public BitSet readBitSet(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
304 return BitSet.valueOf(new long[] { ((DecimalType) readDecimalType(sensorId, parameter)).longValue() });
308 * read an array of decimal values from a sensor
310 * @param sensorId the sensor's full ID
311 * @param parameter device parameters needed for this request
312 * @return a list of DecimalType values
313 * @throws OwException in case an error occurs
315 public List<State> readDecimalTypeArray(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
316 synchronized (owserverConnection) {
317 return owserverConnection.readDecimalTypeArray(parameter.getPath(sensorId));
322 * read a string from a sensor
324 * @param sensorId the sensor's full ID
325 * @param parameter device parameters needed for this request
327 * @throws OwException in case an error occurs
329 public String readString(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
330 synchronized (owserverConnection) {
331 return owserverConnection.readString(parameter.getPath(sensorId));
336 * writes a DecimalType to the sensor
338 * @param sensorId the sensor's full ID
339 * @param parameter device parameters needed for this request
340 * @throws OwException in case an error occurs
342 public void writeDecimalType(SensorId sensorId, OwserverDeviceParameter parameter, DecimalType value)
344 synchronized (owserverConnection) {
345 owserverConnection.writeDecimalType(parameter.getPath(sensorId), value);
350 * writes a BitSet to the sensor
352 * @param sensorId the sensor's full ID
353 * @param parameter device parameters needed for this request
354 * @throws OwException in case an error occurs
356 public void writeBitSet(SensorId sensorId, OwserverDeviceParameter parameter, BitSet value) throws OwException {
357 writeDecimalType(sensorId, parameter, new DecimalType(value.toLongArray()[0]));
361 * returns if this bridge is refreshable
363 * @return true if implementation reports communication ready
365 public boolean isRefreshable() {
370 * updates the thing status with the current connection state
372 * @param connectionState current connection state
374 public void reportConnectionState(OwserverConnectionState connectionState) {
375 logger.debug("Updating owserverconnectionstate to {}", connectionState);
376 switch (connectionState) {
379 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
380 scheduler.schedule(() -> {
381 synchronized (owserverConnection) {
382 owserverConnection.start();
384 }, RECONNECT_AFTER_FAIL_TIME, TimeUnit.MILLISECONDS);
392 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
398 * refreshes channels attached to the bridge
400 * @param now current time
402 public void refreshBridgeChannels(long now) {
403 for (OwfsDirectChannelConfig channelConfig : channelConfigs) {
404 if (now > channelConfig.lastRefresh + channelConfig.refreshCycle) {
407 synchronized (owserverConnection) {
408 if (channelConfig.acceptedItemType.equals("String")) {
409 value = new StringType(owserverConnection.readString(channelConfig.path));
410 } else if (channelConfig.acceptedItemType.equals("Number")) {
411 value = owserverConnection.readDecimalType(channelConfig.path);
413 logger.debug("mismatched configuration, itemType unknown for channel {}",
414 channelConfig.channelUID);
419 final ChannelUID channelUID = channelConfig.channelUID;
420 if (channelUID == null) {
421 throw new OwException("channelUID is null");
423 updateState(channelUID, value);
424 logger.trace("updated {} to {}", channelConfig.channelUID, value);
426 channelConfig.lastRefresh = now;
427 } catch (OwException e) {
428 logger.debug("could not read direct channel {}: {}", channelConfig.channelUID, e.getMessage());
435 public Collection<Class<? extends ThingHandlerService>> getServices() {
436 return Set.of(OwDiscoveryService.class);