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