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