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.sensorcommunity.internal.handler;
15 import java.time.LocalDateTime;
16 import java.util.Optional;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.sensorcommunity.internal.SensorCommunityConfiguration;
23 import org.openhab.binding.sensorcommunity.internal.utils.Constants;
24 import org.openhab.binding.sensorcommunity.internal.utils.DateTimeUtils;
25 import org.openhab.core.thing.ChannelUID;
26 import org.openhab.core.thing.Thing;
27 import org.openhab.core.thing.ThingStatus;
28 import org.openhab.core.thing.ThingStatusDetail;
29 import org.openhab.core.thing.binding.BaseThingHandler;
30 import org.openhab.core.types.Command;
31 import org.openhab.core.types.RefreshType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * The {@link PMHandler} is responsible for handling commands, which are
37 * sent to one of the channels.
39 * @author Bernd Weymann - Initial contribution
42 public abstract class BaseSensorHandler extends BaseThingHandler {
43 private static final SensorCommunityConfiguration DEFAULT_CONFIG = new SensorCommunityConfiguration();
44 private static final String EMPTY = "";
46 protected static final int REFRESH_INTERVAL_MIN = 5;
47 protected final Logger logger = LoggerFactory.getLogger(BaseSensorHandler.class);
48 protected SensorCommunityConfiguration config = DEFAULT_CONFIG;
49 protected ConfigStatus configStatus = ConfigStatus.UNKNOWN;
50 protected ThingStatus myThingStatus = ThingStatus.UNKNOWN;
51 protected UpdateStatus lastUpdateStatus = UpdateStatus.UNKNOWN;
52 protected @Nullable ScheduledFuture<?> refreshJob;
53 private Optional<String> sensorUrl = Optional.empty();
54 private boolean firstUpdate = true;
56 public enum ConfigStatus {
65 public enum UpdateStatus {
74 protected LifecycleStatus lifecycleStatus = LifecycleStatus.UNKNOWN;
76 public enum LifecycleStatus {
83 public BaseSensorHandler(Thing thing) {
88 public void handleCommand(ChannelUID channelUID, Command command) {
89 if (command instanceof RefreshType) {
95 public void initialize() {
97 lifecycleStatus = LifecycleStatus.INITIALIZING;
98 scheduler.execute(this::startUp);
101 private void startUp() {
102 config = getConfigAs(SensorCommunityConfiguration.class);
103 configStatus = checkConfig(config);
104 if (configStatus == ConfigStatus.INTERNAL_SENSOR_OK || configStatus == ConfigStatus.EXTERNAL_SENSOR_OK) {
105 // start getting values
108 // config error, no further actions triggered - Thing Status visible in UI
109 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110 "Configuration not valid. Sensor ID as a number is mandatory!");
112 lifecycleStatus = LifecycleStatus.RUNNING;
115 private void startSchedule() {
116 ScheduledFuture<?> localRefreshJob = refreshJob;
117 if (localRefreshJob != null) {
118 if (localRefreshJob.isCancelled()) {
119 refreshJob = scheduler.scheduleWithFixedDelay(this::dataUpdate, 5, REFRESH_INTERVAL_MIN,
121 } // else - scheduler is already running!
123 refreshJob = scheduler.scheduleWithFixedDelay(this::dataUpdate, 5, REFRESH_INTERVAL_MIN, TimeUnit.MINUTES);
128 public void dispose() {
129 ScheduledFuture<?> localRefreshJob = refreshJob;
130 if (localRefreshJob != null) {
131 localRefreshJob.cancel(true);
133 lifecycleStatus = LifecycleStatus.DISPOSED;
137 * Checks if config is valid - a) not null and b) sensorid is a number
142 private ConfigStatus checkConfig(@Nullable SensorCommunityConfiguration c) {
144 if (c.ipAddress != null && !Constants.EMPTY.equals(c.ipAddress)) {
145 sensorUrl = Optional.of("http://" + c.ipAddress + "/data.json");
146 return ConfigStatus.INTERNAL_SENSOR_OK;
148 if (c.sensorid >= 0) {
149 sensorUrl = Optional.of("http://data.sensor.community/airrohr/v1/sensor/" + c.sensorid + "/");
150 return ConfigStatus.EXTERNAL_SENSOR_OK;
152 return ConfigStatus.SENSOR_ID_NEGATIVE;
156 return ConfigStatus.IS_NULL;
160 public LifecycleStatus getLifecycleStatus() {
161 return lifecycleStatus;
164 protected void dataUpdate() {
165 if (sensorUrl.isPresent()) {
166 HTTPHandler.getHandler().request(sensorUrl.get(), this);
170 public void onResponse(String data) {
172 logger.debug("{} delivers {}", sensorUrl.get(), data);
175 if (configStatus == ConfigStatus.INTERNAL_SENSOR_OK) {
176 lastUpdateStatus = updateChannels("[" + data + "]");
178 lastUpdateStatus = updateChannels(data);
180 statusUpdate(lastUpdateStatus, EMPTY);
183 public void onError(String errorReason) {
184 statusUpdate(UpdateStatus.CONNECTION_EXCEPTION,
185 errorReason + " / " + LocalDateTime.now().format(DateTimeUtils.DTF));
188 protected void statusUpdate(UpdateStatus updateStatus, String details) {
189 if (updateStatus == UpdateStatus.OK) {
190 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
193 switch (updateStatus) {
194 case CONNECTION_ERROR:
195 // start job even first update delivers no data - recovery is possible
197 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
198 "Update failed due to Connection error. Trying to recover in next refresh");
200 case CONNECTION_EXCEPTION:
201 // start job even first update delivers a Connection Exception - recovery is possible
203 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, details);
206 // start job even if first update delivers no values - recovery possible
208 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE,
209 "No values delivered by Sensor. Trying to recover in next refresh");
212 // final status - values from sensor are wrong and manual check is needed
213 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
214 "Sensor values doesn't match - please check if Sensor ID is delivering the correct Thing channel values");
217 // final status - Configuration is wrong
218 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
219 "Error during update - please check your config data");
226 protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
227 myThingStatus = status;
228 super.updateStatus(status, statusDetail, description);
231 protected abstract UpdateStatus updateChannels(@Nullable String json);
233 protected abstract void updateFromCache();