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