]> git.basschouten.com Git - openhab-addons.git/blob
2f3eae8e148afa267a24b78af55c4c197b6c1b36
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.onewire.internal.handler;
14
15 import static org.openhab.binding.onewire.internal.OwBindingConstants.*;
16
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;
24 import java.util.Set;
25 import java.util.concurrent.ConcurrentLinkedQueue;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
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;
56
57 /**
58  * The {@link OwserverBridgeHandler} class implements the refresher and the interface for reading from the bridge
59  *
60  * @author Jan N. Klug - Initial contribution
61  */
62 @NonNullByDefault
63 public class OwserverBridgeHandler extends BaseBridgeHandler {
64     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_OWSERVER);
65
66     private final Logger logger = LoggerFactory.getLogger(OwserverBridgeHandler.class);
67     protected boolean refreshable = false;
68
69     protected ScheduledFuture<?> refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000,
70             TimeUnit.MILLISECONDS);
71
72     // thing update
73     private final Queue<@Nullable Thing> thingPropertiesUpdateQueue = new ConcurrentLinkedQueue<>();
74
75     private static final int RECONNECT_AFTER_FAIL_TIME = 5000; // in ms
76     private final OwserverConnection owserverConnection;
77
78     private final List<OwfsDirectChannelConfig> channelConfigs = new ArrayList<>();
79
80     public OwserverBridgeHandler(Bridge bridge) {
81         super(bridge);
82         this.owserverConnection = new OwserverConnection(this);
83     }
84
85     public OwserverBridgeHandler(Bridge bridge, OwserverConnection owserverConnection) {
86         super(bridge);
87         this.owserverConnection = owserverConnection;
88     }
89
90     @Override
91     public void handleCommand(ChannelUID channelUID, Command command) {
92     }
93
94     @Override
95     public void initialize() {
96         Configuration configuration = getConfig();
97
98         if (configuration.get(CONFIG_ADDRESS) != null) {
99             owserverConnection.setHost((String) configuration.get(CONFIG_ADDRESS));
100         }
101         if (configuration.get(CONFIG_PORT) != null) {
102             owserverConnection.setPort(((BigDecimal) configuration.get(CONFIG_PORT)).intValue());
103         }
104
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);
112                 } else {
113                     logger.info("configuration mismatch: {}", channelConfig);
114                 }
115             }
116         }
117
118         // makes it possible for unit tests to differentiate direct update and
119         // postponed update through the owserverConnection:
120         updateStatus(ThingStatus.UNKNOWN);
121
122         scheduler.execute(() -> {
123             synchronized (owserverConnection) {
124                 owserverConnection.start();
125             }
126         });
127
128         if (refreshTask.isCancelled()) {
129             refreshTask = scheduler.scheduleWithFixedDelay(this::refresh, 1, 1000, TimeUnit.MILLISECONDS);
130         }
131     }
132
133     /**
134      * refresh all sensors on this bridge
135      */
136     private void refresh() {
137         try {
138             long now = System.currentTimeMillis();
139             if (!refreshable) {
140                 logger.trace("refresh requested by thread ID {} denied, as not refresheable",
141                         Thread.currentThread().getId());
142                 return;
143             }
144
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,
150                     thingCount);
151             while (childListIterator.hasNext() && refreshable) {
152                 Thing owThing = childListIterator.next();
153
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);
160                     } else {
161                         logger.trace("{} not initialized, skipping refresh", owThing.getUID());
162                     }
163                 } else {
164                     logger.debug("{} handler missing", owThing.getUID());
165                 }
166                 thingCount--;
167             }
168
169             if (!refreshable) {
170                 logger.trace("refresh aborted, as brige became non-refresheable.");
171                 return;
172             }
173             refreshBridgeChannels(now);
174
175             // update thing properties (only one per refresh cycle)
176             if (!refreshable) {
177                 logger.trace("refresh aborted, as brige became non-refresheable.");
178                 return;
179             }
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) {
186                     try {
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());
195                     }
196                 } else {
197                     logger.debug("{} is missing handler, removing from property update list", updateThing.getUID());
198                 }
199             }
200
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());
204         }
205     }
206
207     @Override
208     public void dispose() {
209         refreshable = false;
210         if (!refreshTask.isCancelled()) {
211             refreshTask.cancel(false);
212         }
213         owserverConnection.stop();
214     }
215
216     /**
217      * schedules a thing for updating the thing properties
218      *
219      * @param thing the thing to be updated
220      */
221     public void scheduleForPropertiesUpdate(Thing thing) {
222         thingPropertiesUpdateQueue.add(thing);
223     }
224
225     /**
226      * get all sensors attached to this bridge
227      *
228      * @return a list of all sensor-IDs
229      */
230     public List<SensorId> getDirectory(String basePath) throws OwException {
231         synchronized (owserverConnection) {
232             return owserverConnection.getDirectory(basePath);
233         }
234     }
235
236     /**
237      * check the presence of a sensor on the bus
238      *
239      * @param sensorId the sensor's full ID
240      * @return ON if present, OFF if missing
241      * @throws OwException in case an error occurs
242      */
243     public State checkPresence(SensorId sensorId) throws OwException {
244         synchronized (owserverConnection) {
245             return owserverConnection.checkPresence(sensorId.getFullPath());
246         }
247     }
248
249     /**
250      * get a sensors type string
251      *
252      * @param sensorId the sensor's full ID
253      * @return a String containing the sensor type
254      * @throws OwException in case an error occurs
255      */
256     public OwSensorType getType(SensorId sensorId) throws OwException {
257         OwSensorType sensorType = OwSensorType.UNKNOWN;
258         synchronized (owserverConnection) {
259             try {
260                 sensorType = OwSensorType.valueOf(owserverConnection.readString(sensorId + "/type"));
261             } catch (IllegalArgumentException ignored) {
262             }
263         }
264         return sensorType;
265     }
266
267     /**
268      * get full sensor information stored in pages (not available on all sensors)
269      *
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
273      */
274     public OwPageBuffer readPages(SensorId sensorId) throws OwException {
275         synchronized (owserverConnection) {
276             return owserverConnection.readPages(sensorId.getFullPath());
277         }
278     }
279
280     /**
281      * read a single decimal value from a sensor
282      *
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
287      */
288     public State readDecimalType(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
289         synchronized (owserverConnection) {
290             return owserverConnection.readDecimalType(parameter.getPath(sensorId));
291         }
292     }
293
294     /**
295      * read a BitSet value from a sensor
296      *
297      * @param sensorId the sensor's full ID
298      * @param parameter device parameters needed for this request
299      * @return a BitSet
300      * @throws OwException in case an error occurs
301      */
302     public BitSet readBitSet(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
303         return BitSet.valueOf(new long[] { ((DecimalType) readDecimalType(sensorId, parameter)).longValue() });
304     }
305
306     /**
307      * read an array of decimal values from a sensor
308      *
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
313      */
314     public List<State> readDecimalTypeArray(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
315         synchronized (owserverConnection) {
316             return owserverConnection.readDecimalTypeArray(parameter.getPath(sensorId));
317         }
318     }
319
320     /**
321      * read a string from a sensor
322      *
323      * @param sensorId the sensor's full ID
324      * @param parameter device parameters needed for this request
325      * @return a String
326      * @throws OwException in case an error occurs
327      */
328     public String readString(SensorId sensorId, OwserverDeviceParameter parameter) throws OwException {
329         synchronized (owserverConnection) {
330             return owserverConnection.readString(parameter.getPath(sensorId));
331         }
332     }
333
334     /**
335      * writes a DecimalType to the sensor
336      *
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
340      */
341     public void writeDecimalType(SensorId sensorId, OwserverDeviceParameter parameter, DecimalType value)
342             throws OwException {
343         synchronized (owserverConnection) {
344             owserverConnection.writeDecimalType(parameter.getPath(sensorId), value);
345         }
346     }
347
348     /**
349      * writes a BitSet to the sensor
350      *
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
354      */
355     public void writeBitSet(SensorId sensorId, OwserverDeviceParameter parameter, BitSet value) throws OwException {
356         writeDecimalType(sensorId, parameter, new DecimalType(value.toLongArray()[0]));
357     }
358
359     /**
360      * returns if this bridge is refreshable
361      *
362      * @return true if implementation reports communication ready
363      */
364     public boolean isRefreshable() {
365         return refreshable;
366     }
367
368     /**
369      * updates the thing status with the current connection state
370      *
371      * @param connectionState current connection state
372      */
373     public void reportConnectionState(OwserverConnectionState connectionState) {
374         logger.debug("Updating owserverconnectionstate to {}", connectionState);
375         switch (connectionState) {
376             case FAILED:
377                 refreshable = false;
378                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
379                 scheduler.schedule(() -> {
380                     synchronized (owserverConnection) {
381                         owserverConnection.start();
382                     }
383                 }, RECONNECT_AFTER_FAIL_TIME, TimeUnit.MILLISECONDS);
384                 break;
385             case STOPPED:
386                 refreshable = false;
387                 break;
388             case OPENED:
389             case CLOSED:
390                 refreshable = true;
391                 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
392                 break;
393         }
394     }
395
396     /**
397      * refreshes channels attached to the bridge
398      *
399      * @param now current time
400      */
401     public void refreshBridgeChannels(long now) {
402         for (OwfsDirectChannelConfig channelConfig : channelConfigs) {
403             if (now > channelConfig.lastRefresh + channelConfig.refreshCycle) {
404                 State value;
405                 try {
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);
411                         } else {
412                             logger.debug("mismatched configuration, itemType unknown for channel {}",
413                                     channelConfig.channelUID);
414                             continue;
415                         }
416                     }
417
418                     final ChannelUID channelUID = channelConfig.channelUID;
419                     if (channelUID == null) {
420                         throw new OwException("channelUID is null");
421                     }
422                     updateState(channelUID, value);
423                     logger.trace("updated {} to {}", channelConfig.channelUID, value);
424
425                     channelConfig.lastRefresh = now;
426                 } catch (OwException e) {
427                     logger.debug("could not read direct channel {}: {}", channelConfig.channelUID, e.getMessage());
428                 }
429             }
430         }
431     }
432
433     @Override
434     public Collection<Class<? extends ThingHandlerService>> getServices() {
435         return Set.of(OwDiscoveryService.class);
436     }
437 }