2 * Copyright (c) 2010-2021 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.foobot.internal.handler;
15 import java.math.BigDecimal;
16 import java.time.Duration;
17 import java.time.Instant;
18 import java.time.ZoneId;
19 import java.time.ZonedDateTime;
22 import javax.measure.Unit;
24 import org.apache.commons.lang.StringUtils;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.foobot.internal.FoobotApiConnector;
28 import org.openhab.binding.foobot.internal.FoobotApiException;
29 import org.openhab.binding.foobot.internal.FoobotBindingConstants;
30 import org.openhab.binding.foobot.internal.json.FoobotDevice;
31 import org.openhab.binding.foobot.internal.json.FoobotJsonData;
32 import org.openhab.binding.foobot.internal.json.FoobotSensor;
33 import org.openhab.core.cache.ExpiringCache;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.thing.Channel;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link FoobotDeviceHandler} is responsible for handling commands, which are sent to one of the channels.
53 * @author Divya Chauhan - Initial contribution
54 * @author George Katsis - Add Bridge thing type
58 public class FoobotDeviceHandler extends BaseThingHandler {
60 private final Logger logger = LoggerFactory.getLogger(FoobotDeviceHandler.class);
61 private final FoobotApiConnector connector;
63 private @NonNullByDefault({}) ExpiringCache<FoobotJsonData> dataCache;
64 private String uuid = "";
66 public FoobotDeviceHandler(final Thing thing, final FoobotApiConnector connector) {
68 this.connector = connector;
72 public void handleCommand(final ChannelUID channelUID, final Command command) {
73 if (command instanceof RefreshType) {
74 final FoobotJsonData sensorData = dataCache.getValue();
76 if (sensorData != null) {
77 updateState(channelUID, sensorDataToState(channelUID.getId(), sensorData));
80 logger.debug("The Foobot binding is read-only and can not handle command {}", command);
85 * @return Returns the uuid associated with this device.
87 public String getUuid() {
92 public void initialize() {
93 logger.debug("Initializing Foobot handler.");
94 uuid = (String) getConfig().get(FoobotBindingConstants.CONFIG_UUID);
96 if (StringUtils.trimToNull(uuid) == null) {
97 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
98 "Parameter 'uuid' is mandatory and must be configured");
101 final FoobotAccountHandler bridgeHandler = getBridgeHandler();
102 final int refreshInterval = bridgeHandler == null ? FoobotBindingConstants.DEFAULT_REFRESH_PERIOD_MINUTES
103 : bridgeHandler.getRefreshInterval();
105 dataCache = new ExpiringCache<>(Duration.ofMinutes(refreshInterval), this::retrieveSensorData);
106 scheduler.execute(this::refreshSensors);
110 * Updates the thing properties as retrieved by the bridge.
112 * @param foobot device parameters.
114 public void handleUpdateProperties(final FoobotDevice foobot) {
115 final Map<String, String> properties = editProperties();
117 properties.put(Thing.PROPERTY_MAC_ADDRESS, foobot.getMac());
118 properties.put(FoobotBindingConstants.PROPERTY_NAME, foobot.getName());
119 updateProperties(properties);
123 * Calls the footbot api to retrieve the sensor data. Sets thing offline in case of errors.
125 * @return returns the retrieved sensor data or null if no data or an error occurred.
127 private @Nullable FoobotJsonData retrieveSensorData() {
128 logger.debug("Refresh sensor data for: {}", uuid);
129 FoobotJsonData sensorData = null;
132 sensorData = connector.getSensorData(uuid);
133 if (sensorData == null) {
134 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "No sensor data received");
137 } catch (FoobotApiException e) {
138 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
140 } catch (RuntimeException e) {
141 logger.debug("Error requesting sensor data: ", e);
142 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
145 if (getThing().getStatus() != ThingStatus.ONLINE) {
146 updateStatus(ThingStatus.ONLINE);
148 final FoobotAccountHandler bridgeHandler = getBridgeHandler();
150 if (bridgeHandler != null) {
151 bridgeHandler.updateRemainingLimitStatus();
157 * Refreshes the device channels.
159 public void refreshSensors() {
160 final FoobotJsonData sensorData = dataCache.getValue();
162 if (sensorData != null) {
163 for (final Channel channel : getThing().getChannels()) {
164 final ChannelUID channelUid = channel.getUID();
166 updateState(channelUid, sensorDataToState(channelUid.getId(), sensorData));
168 updateTime(sensorData);
172 private void updateTime(final FoobotJsonData sensorData) {
173 final State lastTime = sensorDataToState(FoobotSensor.TIME.getChannelId(), sensorData);
175 if (lastTime instanceof DecimalType) {
176 ((DecimalType) lastTime).intValue();
181 public void dispose() {
182 logger.debug("Disposing the Foobot handler.");
185 protected State sensorDataToState(final String channelId, final FoobotJsonData data) {
186 final FoobotSensor sensor = FoobotSensor.findSensorByChannelId(channelId);
188 if (sensor == null || data.getSensors() == null || data.getDatapoints() == null
189 || data.getDatapoints().isEmpty()) {
190 return UnDefType.UNDEF;
192 final int sensorIndex = data.getSensors().indexOf(sensor.getDataKey());
194 if (sensorIndex == -1) {
195 return UnDefType.UNDEF;
197 final String value = data.getDatapoints().get(0).get(sensorIndex);
198 final String unit = data.getUnits().get(sensorIndex);
201 return UnDefType.UNDEF;
203 final Unit<?> stateUnit = sensor.getUnit(unit);
204 if (sensor == FoobotSensor.TIME) {
205 return new DateTimeType(
206 ZonedDateTime.ofInstant(Instant.ofEpochSecond(Long.parseLong(value)), ZoneId.systemDefault()));
207 } else if (stateUnit == null) {
208 return new DecimalType(value);
210 return new QuantityType(new BigDecimal(value), stateUnit);
215 private @Nullable FoobotAccountHandler getBridgeHandler() {
216 return getBridge() != null && getBridge().getHandler() instanceof FoobotAccountHandler
217 ? (FoobotAccountHandler) getBridge().getHandler()