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.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.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.foobot.internal.FoobotApiConnector;
27 import org.openhab.binding.foobot.internal.FoobotApiException;
28 import org.openhab.binding.foobot.internal.FoobotBindingConstants;
29 import org.openhab.binding.foobot.internal.json.FoobotDevice;
30 import org.openhab.binding.foobot.internal.json.FoobotJsonData;
31 import org.openhab.binding.foobot.internal.json.FoobotSensor;
32 import org.openhab.core.cache.ExpiringCache;
33 import org.openhab.core.library.types.DateTimeType;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.thing.Channel;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.openhab.core.types.State;
45 import org.openhab.core.types.UnDefType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link FoobotDeviceHandler} is responsible for handling commands, which are sent to one of the channels.
52 * @author Divya Chauhan - Initial contribution
53 * @author George Katsis - Add Bridge thing type
57 public class FoobotDeviceHandler extends BaseThingHandler {
59 private final Logger logger = LoggerFactory.getLogger(FoobotDeviceHandler.class);
60 private final FoobotApiConnector connector;
62 private @NonNullByDefault({}) ExpiringCache<FoobotJsonData> dataCache;
63 private String uuid = "";
65 public FoobotDeviceHandler(final Thing thing, final FoobotApiConnector connector) {
67 this.connector = connector;
71 public void handleCommand(final ChannelUID channelUID, final Command command) {
72 if (command instanceof RefreshType) {
73 final FoobotJsonData sensorData = dataCache.getValue();
75 if (sensorData != null) {
76 updateState(channelUID, sensorDataToState(channelUID.getId(), sensorData));
79 logger.debug("The Foobot binding is read-only and can not handle command {}", command);
84 * @return Returns the uuid associated with this device.
86 public String getUuid() {
91 public void initialize() {
92 logger.debug("Initializing Foobot handler.");
93 uuid = (String) getConfig().get(FoobotBindingConstants.CONFIG_UUID);
96 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
97 "Parameter 'uuid' is mandatory and must be configured");
100 final FoobotAccountHandler bridgeHandler = getBridgeHandler();
101 final int refreshInterval = bridgeHandler == null ? FoobotBindingConstants.DEFAULT_REFRESH_PERIOD_MINUTES
102 : bridgeHandler.getRefreshInterval();
104 dataCache = new ExpiringCache<>(Duration.ofMinutes(refreshInterval), this::retrieveSensorData);
105 scheduler.execute(this::refreshSensors);
109 * Updates the thing properties as retrieved by the bridge.
111 * @param foobot device parameters.
113 public void handleUpdateProperties(final FoobotDevice foobot) {
114 final Map<String, String> properties = editProperties();
116 properties.put(Thing.PROPERTY_MAC_ADDRESS, foobot.getMac());
117 properties.put(FoobotBindingConstants.PROPERTY_NAME, foobot.getName());
118 updateProperties(properties);
122 * Calls the footbot api to retrieve the sensor data. Sets thing offline in case of errors.
124 * @return returns the retrieved sensor data or null if no data or an error occurred.
126 private @Nullable FoobotJsonData retrieveSensorData() {
127 logger.debug("Refresh sensor data for: {}", uuid);
128 FoobotJsonData sensorData = null;
131 sensorData = connector.getSensorData(uuid);
132 if (sensorData == null) {
133 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "No sensor data received");
136 } catch (FoobotApiException e) {
137 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
139 } catch (RuntimeException e) {
140 logger.debug("Error requesting sensor data: ", e);
141 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
144 if (getThing().getStatus() != ThingStatus.ONLINE) {
145 updateStatus(ThingStatus.ONLINE);
147 final FoobotAccountHandler bridgeHandler = getBridgeHandler();
149 if (bridgeHandler != null) {
150 bridgeHandler.updateRemainingLimitStatus();
156 * Refreshes the device channels.
158 public void refreshSensors() {
159 final FoobotJsonData sensorData = dataCache.getValue();
161 if (sensorData != null) {
162 for (final Channel channel : getThing().getChannels()) {
163 final ChannelUID channelUid = channel.getUID();
165 updateState(channelUid, sensorDataToState(channelUid.getId(), sensorData));
167 updateTime(sensorData);
171 private void updateTime(final FoobotJsonData sensorData) {
172 final State lastTime = sensorDataToState(FoobotSensor.TIME.getChannelId(), sensorData);
174 if (lastTime instanceof DecimalType) {
175 ((DecimalType) lastTime).intValue();
180 public void dispose() {
181 logger.debug("Disposing the Foobot handler.");
184 protected State sensorDataToState(final String channelId, final FoobotJsonData data) {
185 final FoobotSensor sensor = FoobotSensor.findSensorByChannelId(channelId);
187 if (sensor == null || data.getSensors() == null || data.getDatapoints() == null
188 || data.getDatapoints().isEmpty()) {
189 return UnDefType.UNDEF;
191 final int sensorIndex = data.getSensors().indexOf(sensor.getDataKey());
193 if (sensorIndex == -1) {
194 return UnDefType.UNDEF;
196 final String value = data.getDatapoints().get(0).get(sensorIndex);
197 final String unit = data.getUnits().get(sensorIndex);
200 return UnDefType.UNDEF;
202 final Unit<?> stateUnit = sensor.getUnit(unit);
203 if (sensor == FoobotSensor.TIME) {
204 return new DateTimeType(
205 ZonedDateTime.ofInstant(Instant.ofEpochSecond(Long.parseLong(value)), ZoneId.systemDefault()));
206 } else if (stateUnit == null) {
207 return new DecimalType(value);
209 return new QuantityType(new BigDecimal(value), stateUnit);
214 private @Nullable FoobotAccountHandler getBridgeHandler() {
215 return getBridge() != null && getBridge().getHandler() instanceof FoobotAccountHandler
216 ? (FoobotAccountHandler) getBridge().getHandler()