]> git.basschouten.com Git - openhab-addons.git/blob
1f5f0e428e65ef873acd2be99fd6c4e55a2ff34e
[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.deconz.internal.handler;
14
15 import static org.openhab.binding.deconz.internal.BindingConstants.*;
16
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Objects;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.openhab.binding.deconz.internal.Util;
23 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
24 import org.openhab.binding.deconz.internal.dto.SensorConfig;
25 import org.openhab.binding.deconz.internal.dto.SensorMessage;
26 import org.openhab.binding.deconz.internal.dto.SensorState;
27 import org.openhab.binding.deconz.internal.types.ResourceType;
28 import org.openhab.core.thing.Channel;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.binding.builder.ThingBuilder;
34 import org.openhab.core.thing.type.ChannelKind;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.UnDefType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.google.gson.Gson;
41
42 /**
43  * This sensor Thing doesn't establish any connections, that is done by the bridge Thing.
44  *
45  * It waits for the bridge to come online, grab the websocket connection and bridge configuration
46  * and registers to the websocket connection as a listener.
47  *
48  * A REST API call is made to get the initial sensor state.
49  *
50  * Every sensor and switch is supported by this Thing, because a unified state is kept
51  * in {@link #sensorState}. Every field that got received by the REST API for this specific
52  * sensor is published to the framework.
53  *
54  * @author David Graeff - Initial contribution
55  * @author Lukas Agethen - Refactored to provide better extensibility
56  */
57 @NonNullByDefault
58 public abstract class SensorBaseThingHandler extends DeconzBaseThingHandler {
59     private final Logger logger = LoggerFactory.getLogger(SensorBaseThingHandler.class);
60     /**
61      * The sensor state. Contains all possible fields for all supported sensors and switches
62      */
63     protected SensorConfig sensorConfig = new SensorConfig();
64     protected SensorState sensorState = new SensorState();
65     /**
66      * Prevent a dispose/init cycle while this flag is set. Use for property updates
67      */
68     private boolean ignoreConfigurationUpdate;
69
70     public SensorBaseThingHandler(Thing thing, Gson gson) {
71         super(thing, gson, ResourceType.SENSORS);
72     }
73
74     @Override
75     public abstract void handleCommand(ChannelUID channelUID, Command command);
76
77     protected abstract boolean createTypeSpecificChannels(ThingBuilder thingBuilder, SensorConfig sensorState,
78             SensorState sensorConfig);
79
80     protected abstract List<String> getConfigChannels();
81
82     @Override
83     public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
84         if (!ignoreConfigurationUpdate) {
85             super.handleConfigurationUpdate(configurationParameters);
86         }
87     }
88
89     @Override
90     protected void processStateResponse(DeconzBaseMessage stateResponse) {
91         if (!(stateResponse instanceof SensorMessage sensorMessage)) {
92             return;
93         }
94
95         sensorConfig = Objects.requireNonNullElse(sensorMessage.config, new SensorConfig());
96         sensorState = Objects.requireNonNullElse(sensorMessage.state, new SensorState());
97
98         // Add some information about the sensor
99         if (!sensorConfig.reachable) {
100             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
101             return;
102         }
103
104         Map<String, String> editProperties = editProperties();
105         editProperties.put(UNIQUE_ID, sensorMessage.uniqueid);
106         editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, sensorMessage.swversion);
107         editProperties.put(Thing.PROPERTY_VENDOR, sensorMessage.manufacturername);
108         editProperties.put(Thing.PROPERTY_MODEL_ID, sensorMessage.modelid);
109
110         ignoreConfigurationUpdate = true;
111
112         updateProperties(editProperties);
113
114         // Some sensors support optional channels
115         // (see https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/Supported-Devices#sensors)
116         // any battery-powered sensor
117         ThingBuilder thingBuilder = editThing();
118         boolean thingEdited = false;
119
120         if (sensorConfig.battery != null) {
121             if (createChannel(thingBuilder, CHANNEL_BATTERY_LEVEL, ChannelKind.STATE)) {
122                 thingEdited = true;
123             }
124             if (createChannel(thingBuilder, CHANNEL_BATTERY_LOW, ChannelKind.STATE)) {
125                 thingEdited = true;
126             }
127         } else if (sensorState.lowbattery != null) {
128             // if sensorConfig.battery != null the channel is already added
129             if (createChannel(thingBuilder, CHANNEL_BATTERY_LOW, ChannelKind.STATE)) {
130                 thingEdited = true;
131             }
132         }
133
134         if (createTypeSpecificChannels(thingBuilder, sensorConfig, sensorState)) {
135             thingEdited = true;
136         }
137
138         if (checkLastSeen(thingBuilder, sensorMessage.lastseen)) {
139             thingEdited = true;
140         }
141
142         // if the thing was edited, we update it now
143         if (thingEdited) {
144             logger.debug("Thing configuration changed, updating thing.");
145             updateThing(thingBuilder.build());
146         }
147         ignoreConfigurationUpdate = false;
148
149         // Initial data
150         updateChannels(sensorConfig);
151         updateChannels(sensorState, true);
152
153         updateStatus(ThingStatus.ONLINE);
154     }
155
156     /**
157      * Update channel value from {@link SensorConfig} object - override to include further channels
158      *
159      * @param channelUID
160      * @param newConfig
161      */
162     protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
163         Integer batteryLevel = newConfig.battery;
164         if (batteryLevel != null) {
165             switch (channelUID.getId()) {
166                 case CHANNEL_BATTERY_LEVEL -> updateDecimalTypeChannel(channelUID, batteryLevel.longValue());
167                 case CHANNEL_BATTERY_LOW -> updateSwitchChannel(channelUID, batteryLevel <= 10);
168                 // other cases covered by subclass
169             }
170         }
171     }
172
173     /**
174      * Update channel value from {@link SensorState} object - override to include further channels
175      *
176      * @param channelUID
177      * @param newState
178      * @param initializing
179      */
180     protected void valueUpdated(ChannelUID channelUID, SensorState newState, boolean initializing) {
181         switch (channelUID.getId()) {
182             case CHANNEL_LAST_UPDATED -> {
183                 String lastUpdated = newState.lastupdated;
184                 if (lastUpdated != null && !"none".equals(lastUpdated)) {
185                     updateState(channelUID, Util.convertTimestampToDateTime(lastUpdated));
186                 } else if ("none".equals(lastUpdated)) {
187                     updateState(channelUID, UnDefType.UNDEF);
188                 }
189             }
190             case CHANNEL_BATTERY_LOW -> updateSwitchChannel(channelUID, newState.lowbattery);
191             // other cases covered by subclass
192         }
193     }
194
195     @Override
196     public void messageReceived(DeconzBaseMessage message) {
197         logger.trace("{} received {}", thing.getUID(), message);
198         if (message instanceof SensorMessage sensorMessage) {
199             SensorConfig sensorConfig = sensorMessage.config;
200             if (sensorConfig != null) {
201                 if (sensorConfig.reachable) {
202                     updateStatus(ThingStatus.ONLINE);
203                     updateChannels(sensorConfig);
204                 } else {
205                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
206                 }
207             }
208             SensorState sensorState = sensorMessage.state;
209             if (sensorState != null) {
210                 updateChannels(sensorState, false);
211             }
212         }
213     }
214
215     private void updateChannels(SensorConfig newConfig) {
216         this.sensorConfig = newConfig;
217         List<String> configChannels = getConfigChannels();
218         thing.getChannels().stream().map(Channel::getUID)
219                 .filter(channelUID -> configChannels.contains(channelUID.getId()))
220                 .forEach((channelUID) -> valueUpdated(channelUID, newConfig));
221     }
222
223     protected void updateChannels(SensorState newState, boolean initializing) {
224         sensorState = newState;
225         thing.getChannels().forEach(channel -> valueUpdated(channel.getUID(), newState, initializing));
226     }
227 }