2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.deconz.internal.handler;
15 import static org.openhab.binding.deconz.internal.BindingConstants.*;
17 import java.util.List;
19 import java.util.Objects;
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;
40 import com.google.gson.Gson;
43 * This sensor Thing doesn't establish any connections, that is done by the bridge Thing.
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.
48 * A REST API call is made to get the initial sensor state.
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.
54 * @author David Graeff - Initial contribution
55 * @author Lukas Agethen - Refactored to provide better extensibility
58 public abstract class SensorBaseThingHandler extends DeconzBaseThingHandler {
59 private final Logger logger = LoggerFactory.getLogger(SensorBaseThingHandler.class);
61 * The sensor state. Contains all possible fields for all supported sensors and switches
63 protected SensorConfig sensorConfig = new SensorConfig();
64 protected SensorState sensorState = new SensorState();
66 * Prevent a dispose/init cycle while this flag is set. Use for property updates
68 private boolean ignoreConfigurationUpdate;
70 public SensorBaseThingHandler(Thing thing, Gson gson) {
71 super(thing, gson, ResourceType.SENSORS);
75 public abstract void handleCommand(ChannelUID channelUID, Command command);
77 protected abstract boolean createTypeSpecificChannels(ThingBuilder thingBuilder, SensorConfig sensorState,
78 SensorState sensorConfig);
80 protected abstract List<String> getConfigChannels();
83 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
84 if (!ignoreConfigurationUpdate) {
85 super.handleConfigurationUpdate(configurationParameters);
90 protected void processStateResponse(DeconzBaseMessage stateResponse) {
91 if (!(stateResponse instanceof SensorMessage sensorMessage)) {
95 sensorConfig = Objects.requireNonNullElse(sensorMessage.config, new SensorConfig());
96 sensorState = Objects.requireNonNullElse(sensorMessage.state, new SensorState());
98 // Add some information about the sensor
99 if (!sensorConfig.reachable) {
100 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
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);
110 ignoreConfigurationUpdate = true;
112 updateProperties(editProperties);
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;
120 if (sensorConfig.battery != null) {
121 if (createChannel(thingBuilder, CHANNEL_BATTERY_LEVEL, ChannelKind.STATE)) {
124 if (createChannel(thingBuilder, CHANNEL_BATTERY_LOW, ChannelKind.STATE)) {
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)) {
134 if (createTypeSpecificChannels(thingBuilder, sensorConfig, sensorState)) {
138 if (checkLastSeen(thingBuilder, sensorMessage.lastseen)) {
142 // if the thing was edited, we update it now
144 logger.debug("Thing configuration changed, updating thing.");
145 updateThing(thingBuilder.build());
147 ignoreConfigurationUpdate = false;
150 updateChannels(sensorConfig);
151 updateChannels(sensorState, true);
153 updateStatus(ThingStatus.ONLINE);
157 * Update channel value from {@link SensorConfig} object - override to include further channels
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
174 * Update channel value from {@link SensorState} object - override to include further channels
178 * @param initializing
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);
190 case CHANNEL_BATTERY_LOW -> updateSwitchChannel(channelUID, newState.lowbattery);
191 // other cases covered by subclass
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);
205 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
208 SensorState sensorState = sensorMessage.state;
209 if (sensorState != null) {
210 updateChannels(sensorState, false);
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));
223 protected void updateChannels(SensorState newState, boolean initializing) {
224 sensorState = newState;
225 thing.getChannels().forEach(channel -> valueUpdated(channel.getUID(), newState, initializing));